@node-red/editor-client 4.1.1 → 5.0.0-beta.0
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/locales/en-US/editor.json +1 -0
- package/package.json +1 -1
- package/public/red/about +39 -0
- package/public/red/images/explorer.svg +1 -0
- package/public/red/keymap.json +2 -1
- package/public/red/red.js +2008 -647
- package/public/red/red.min.js +3 -3
- package/public/red/style.min.css +2 -2
- package/public/red/tours/4.1/images/missing-modules.png +0 -0
- package/public/red/tours/4.1/images/node-docs.png +0 -0
- package/public/red/tours/4.1/images/update-notification.png +0 -0
- package/public/red/tours/4.1/welcome.js +126 -0
- package/public/red/tours/welcome.js +25 -104
package/public/red/red.js
CHANGED
|
@@ -878,7 +878,7 @@ var RED = (function() {
|
|
|
878
878
|
RED.user.init();
|
|
879
879
|
RED.notifications.init();
|
|
880
880
|
RED.library.init();
|
|
881
|
-
RED.
|
|
881
|
+
RED.sidebar.init();
|
|
882
882
|
RED.eventLog.init();
|
|
883
883
|
|
|
884
884
|
if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) {
|
|
@@ -887,7 +887,6 @@ var RED = (function() {
|
|
|
887
887
|
console.log("Palette editor disabled");
|
|
888
888
|
}
|
|
889
889
|
|
|
890
|
-
RED.sidebar.init();
|
|
891
890
|
|
|
892
891
|
if (RED.settings.theme("projects.enabled",false)) {
|
|
893
892
|
RED.projects.init();
|
|
@@ -904,23 +903,34 @@ var RED = (function() {
|
|
|
904
903
|
RED.diagnostics.init();
|
|
905
904
|
RED.diff.init();
|
|
906
905
|
|
|
907
|
-
|
|
908
906
|
RED.deploy.init(RED.settings.theme("deployButton",null));
|
|
907
|
+
RED.keyboard.init(() => {
|
|
908
|
+
buildMainMenu();
|
|
909
909
|
|
|
910
|
-
|
|
911
|
-
|
|
910
|
+
// Register the core set of sidebar panels now the menu is ready to receive items
|
|
911
|
+
RED.palette.init();
|
|
912
|
+
RED.sidebar.info.init();
|
|
913
|
+
RED.sidebar.help.init();
|
|
914
|
+
RED.sidebar.config.init();
|
|
915
|
+
RED.sidebar.context.init();
|
|
916
|
+
// hide sidebar at start if screen rather narrow...
|
|
917
|
+
if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar", false); }
|
|
912
918
|
|
|
913
|
-
|
|
914
|
-
RED.runtime.init()
|
|
919
|
+
RED.envVar.init();
|
|
915
920
|
|
|
916
|
-
|
|
917
|
-
RED.
|
|
918
|
-
}
|
|
919
|
-
RED.comms.connect();
|
|
921
|
+
RED.nodes.init();
|
|
922
|
+
RED.runtime.init()
|
|
920
923
|
|
|
921
|
-
|
|
924
|
+
if (RED.settings.theme("multiplayer.enabled",false)) {
|
|
925
|
+
RED.multiplayer.init()
|
|
926
|
+
}
|
|
927
|
+
RED.comms.connect();
|
|
922
928
|
|
|
923
|
-
|
|
929
|
+
$("#red-ui-main-container").show();
|
|
930
|
+
RED.events.emit("sidebar:resize")
|
|
931
|
+
|
|
932
|
+
loadPluginList();
|
|
933
|
+
});
|
|
924
934
|
}
|
|
925
935
|
|
|
926
936
|
|
|
@@ -929,13 +939,18 @@ var RED = (function() {
|
|
|
929
939
|
var logo = $('<span class="red-ui-header-logo"></span>').appendTo(header);
|
|
930
940
|
$('<ul class="red-ui-header-toolbar hide"></ul>').appendTo(header);
|
|
931
941
|
$('<div id="red-ui-header-shade" class="hide"></div>').appendTo(header);
|
|
932
|
-
$('<div id="red-ui-main-container"
|
|
942
|
+
$('<div id="red-ui-main-container">'+
|
|
943
|
+
'<div id="red-ui-sidebar-left"></div>'+
|
|
933
944
|
'<div id="red-ui-workspace"></div>'+
|
|
934
|
-
'<div id="red-ui-editor-stack" tabindex="-1"></div>'+
|
|
935
|
-
'<div id="red-ui-palette"></div>'+
|
|
936
945
|
'<div id="red-ui-sidebar"></div>'+
|
|
937
|
-
'<div id="red-ui-
|
|
946
|
+
'<div id="red-ui-editor-stack" tabindex="-1"></div>'+
|
|
947
|
+
// '<div id="red-ui-palette"></div>'+
|
|
938
948
|
'</div>').appendTo(options.target);
|
|
949
|
+
|
|
950
|
+
// Don't use the `hide` class on this container, as the show reverts it to block rather
|
|
951
|
+
// than the expected flex. So hide via jQuery as it'll track the show state internally.
|
|
952
|
+
options.target.find('#red-ui-main-container').hide()
|
|
953
|
+
|
|
939
954
|
$('<div id="red-ui-editor-plugin-configs"></div>').appendTo(options.target);
|
|
940
955
|
$('<div id="red-ui-editor-node-configs"></div>').appendTo(options.target);
|
|
941
956
|
$('<div id="red-ui-full-shade" class="hide"></div>').appendTo(options.target);
|
|
@@ -2139,8 +2154,8 @@ RED.comms = (function() {
|
|
|
2139
2154
|
subscribers[i](msg.topic,msg.data);
|
|
2140
2155
|
} catch (error) {
|
|
2141
2156
|
// need to decide what to do with this uncaught error
|
|
2142
|
-
console.warn('Uncaught error from RED.comms.subscribe: ' +
|
|
2143
|
-
console.warn(
|
|
2157
|
+
console.warn('Uncaught error from RED.comms.subscribe: ' + error.toString())
|
|
2158
|
+
console.warn(error)
|
|
2144
2159
|
}
|
|
2145
2160
|
}
|
|
2146
2161
|
}
|
|
@@ -9321,7 +9336,15 @@ RED.history = (function() {
|
|
|
9321
9336
|
}
|
|
9322
9337
|
}
|
|
9323
9338
|
}
|
|
9324
|
-
|
|
9339
|
+
if (ev.node.type === 'subflow') {
|
|
9340
|
+
// Ensure ports get a refresh in case of a label change
|
|
9341
|
+
if (ev.changes.inputLabels) {
|
|
9342
|
+
ev.node.in.forEach(function(input) { input.dirty = true; });
|
|
9343
|
+
}
|
|
9344
|
+
if (ev.changes.outputLabels) {
|
|
9345
|
+
ev.node.out.forEach(function(output) { output.dirty = true; });
|
|
9346
|
+
}
|
|
9347
|
+
}
|
|
9325
9348
|
ev.node.dirty = true;
|
|
9326
9349
|
ev.node.changed = ev.changed;
|
|
9327
9350
|
|
|
@@ -9632,8 +9655,8 @@ RED.history = (function() {
|
|
|
9632
9655
|
return {
|
|
9633
9656
|
//TODO: this function is a placeholder until there is a 'save' event that can be listened to
|
|
9634
9657
|
markAllDirty: function() {
|
|
9635
|
-
for (
|
|
9636
|
-
|
|
9658
|
+
for (const event of [...undoHistory, ...redoHistory]) {
|
|
9659
|
+
event.dirty = true;
|
|
9637
9660
|
}
|
|
9638
9661
|
},
|
|
9639
9662
|
list: function() {
|
|
@@ -13195,7 +13218,7 @@ RED.menu = (function() {
|
|
|
13195
13218
|
} else {
|
|
13196
13219
|
for (var i=0;i<groupItems.length;i++) {
|
|
13197
13220
|
var groupItem = groupItems[i];
|
|
13198
|
-
var label = $(groupItem).find(".red-ui-menu-label").
|
|
13221
|
+
var label = $(groupItem).find(".red-ui-menu-label span").text();
|
|
13199
13222
|
if (opt.label < label) {
|
|
13200
13223
|
$(groupItem).before(item);
|
|
13201
13224
|
break;
|
|
@@ -14861,7 +14884,6 @@ RED.tabs = (function() {
|
|
|
14861
14884
|
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
|
|
14862
14885
|
}
|
|
14863
14886
|
}
|
|
14864
|
-
|
|
14865
14887
|
}
|
|
14866
14888
|
|
|
14867
14889
|
ul.find("li.red-ui-tab a")
|
|
@@ -15361,7 +15383,8 @@ RED.tabs = (function() {
|
|
|
15361
15383
|
pinnedButtons["__menu__"].appendTo(collapsedButtonsRow);
|
|
15362
15384
|
updateTabWidths();
|
|
15363
15385
|
}
|
|
15364
|
-
}
|
|
15386
|
+
},
|
|
15387
|
+
container: wrapper
|
|
15365
15388
|
}
|
|
15366
15389
|
return tabAPI;
|
|
15367
15390
|
}
|
|
@@ -17791,7 +17814,7 @@ RED.deploy = (function() {
|
|
|
17791
17814
|
}
|
|
17792
17815
|
|
|
17793
17816
|
function updateLockedState() {
|
|
17794
|
-
if (RED.
|
|
17817
|
+
if (!RED.user.hasPermission('flows.write')) {
|
|
17795
17818
|
$(".red-ui-deploy-button-group").addClass("readOnly");
|
|
17796
17819
|
$("#red-ui-header-button-deploy").addClass("disabled");
|
|
17797
17820
|
} else {
|
|
@@ -17924,13 +17947,13 @@ RED.deploy = (function() {
|
|
|
17924
17947
|
$("#red-ui-header-shade").show();
|
|
17925
17948
|
$("#red-ui-editor-shade").show();
|
|
17926
17949
|
$("#red-ui-palette-shade").show();
|
|
17927
|
-
$("
|
|
17950
|
+
$(".red-ui-sidebar-shade").show();
|
|
17928
17951
|
}
|
|
17929
17952
|
function shadeHide() {
|
|
17930
17953
|
$("#red-ui-header-shade").hide();
|
|
17931
17954
|
$("#red-ui-editor-shade").hide();
|
|
17932
17955
|
$("#red-ui-palette-shade").hide();
|
|
17933
|
-
$("
|
|
17956
|
+
$(".red-ui-sidebar-shade").hide();
|
|
17934
17957
|
}
|
|
17935
17958
|
function deployButtonSetBusy(){
|
|
17936
17959
|
$(".red-ui-deploy-button-content").css('opacity',0);
|
|
@@ -19744,11 +19767,11 @@ RED.diagnostics = (function () {
|
|
|
19744
19767
|
diffTable.finish();
|
|
19745
19768
|
diffTable.list.show();
|
|
19746
19769
|
},300);
|
|
19747
|
-
$("
|
|
19770
|
+
$(".red-ui-sidebar-shade").show();
|
|
19748
19771
|
},
|
|
19749
19772
|
close: function() {
|
|
19750
19773
|
diffVisible = false;
|
|
19751
|
-
$("
|
|
19774
|
+
$(".red-ui-sidebar-shade").hide();
|
|
19752
19775
|
|
|
19753
19776
|
},
|
|
19754
19777
|
show: function() {
|
|
@@ -22675,11 +22698,29 @@ RED.view = (function() {
|
|
|
22675
22698
|
node_height = 30,
|
|
22676
22699
|
dblClickInterval = 650;
|
|
22677
22700
|
|
|
22701
|
+
var cancelInProgressAnimation = null; // For smooth zoom animation
|
|
22702
|
+
|
|
22678
22703
|
var touchLongPressTimeout = 1000,
|
|
22679
22704
|
startTouchDistance = 0,
|
|
22680
22705
|
startTouchCenter = [],
|
|
22681
22706
|
moveTouchCenter = [],
|
|
22682
|
-
touchStartTime = 0
|
|
22707
|
+
touchStartTime = 0,
|
|
22708
|
+
gesture = {};
|
|
22709
|
+
|
|
22710
|
+
var spacebarPressed = false;
|
|
22711
|
+
|
|
22712
|
+
// Momentum scrolling state
|
|
22713
|
+
var scrollVelocity = { x: 0, y: 0 };
|
|
22714
|
+
var lastScrollTime = 0;
|
|
22715
|
+
var lastScrollPos = { x: 0, y: 0 };
|
|
22716
|
+
var scrollAnimationId = null;
|
|
22717
|
+
var momentumActive = false;
|
|
22718
|
+
|
|
22719
|
+
// Bounce effect parameters
|
|
22720
|
+
var BOUNCE_DAMPING = 0.6;
|
|
22721
|
+
var BOUNCE_TENSION = 0.3;
|
|
22722
|
+
var MIN_VELOCITY = 0.5;
|
|
22723
|
+
var FRICTION = 0.95;
|
|
22683
22724
|
|
|
22684
22725
|
var workspaceScrollPositions = {};
|
|
22685
22726
|
|
|
@@ -22742,6 +22783,7 @@ RED.view = (function() {
|
|
|
22742
22783
|
let suggestedLinks = [];
|
|
22743
22784
|
let suggestedJunctions = [];
|
|
22744
22785
|
|
|
22786
|
+
let forceFullRedraw = false
|
|
22745
22787
|
// Note: these are the permitted status colour aliases. The actual RGB values
|
|
22746
22788
|
// are set in the CSS - flow.scss/colors.scss
|
|
22747
22789
|
const status_colours = {
|
|
@@ -22955,6 +22997,24 @@ RED.view = (function() {
|
|
|
22955
22997
|
function init() {
|
|
22956
22998
|
|
|
22957
22999
|
chart = $("#red-ui-workspace-chart");
|
|
23000
|
+
|
|
23001
|
+
// Add invisible spacer div to ensure scrollable area matches canvas dimensions
|
|
23002
|
+
// At minimum zoom with "cover" behavior, SVG may be smaller than viewport in one dimension
|
|
23003
|
+
// This spacer forces the browser to calculate scrollWidth/Height based on full canvas size
|
|
23004
|
+
// Browser's maxScroll = scrollWidth - viewport will then correctly show canvas edges
|
|
23005
|
+
var scrollSpacer = $('<div>')
|
|
23006
|
+
.css({
|
|
23007
|
+
position: 'absolute',
|
|
23008
|
+
top: 0,
|
|
23009
|
+
left: 0,
|
|
23010
|
+
width: space_width + 'px',
|
|
23011
|
+
height: space_height + 'px',
|
|
23012
|
+
pointerEvents: 'none',
|
|
23013
|
+
visibility: 'hidden'
|
|
23014
|
+
})
|
|
23015
|
+
.attr('id', 'red-ui-workspace-scroll-spacer')
|
|
23016
|
+
.appendTo(chart);
|
|
23017
|
+
|
|
22958
23018
|
chart.on('contextmenu', function(evt) {
|
|
22959
23019
|
if (RED.view.DEBUG) {
|
|
22960
23020
|
console.warn("contextmenu", { mouse_mode, event: d3.event });
|
|
@@ -22999,8 +23059,9 @@ RED.view = (function() {
|
|
|
22999
23059
|
lasso.remove();
|
|
23000
23060
|
lasso = null;
|
|
23001
23061
|
}
|
|
23002
|
-
} else if (mouse_mode === RED.state.PANNING
|
|
23003
|
-
|
|
23062
|
+
} else if (mouse_mode === RED.state.PANNING) {
|
|
23063
|
+
// ensure the cursor is set to grab when re-entering the canvas while panning
|
|
23064
|
+
outer.style('cursor', 'grabbing');
|
|
23004
23065
|
} else if (slicePath) {
|
|
23005
23066
|
if (d3.event.buttons !== 2) {
|
|
23006
23067
|
slicePath.remove();
|
|
@@ -23017,11 +23078,15 @@ RED.view = (function() {
|
|
|
23017
23078
|
if (RED.touch.radialMenu.active()) {
|
|
23018
23079
|
return;
|
|
23019
23080
|
}
|
|
23081
|
+
// End gesture when touches end
|
|
23082
|
+
RED.view.zoomAnimator.endGesture();
|
|
23020
23083
|
canvasMouseUp.call(this);
|
|
23021
23084
|
})
|
|
23022
23085
|
.on("touchcancel", function() {
|
|
23023
23086
|
if (RED.view.DEBUG) { console.warn("eventLayer.touchcancel", mouse_mode); }
|
|
23024
23087
|
d3.event.preventDefault();
|
|
23088
|
+
// End gesture when touches are cancelled
|
|
23089
|
+
RED.view.zoomAnimator.endGesture();
|
|
23025
23090
|
canvasMouseUp.call(this);
|
|
23026
23091
|
})
|
|
23027
23092
|
.on("touchstart", function() {
|
|
@@ -23047,6 +23112,20 @@ RED.view = (function() {
|
|
|
23047
23112
|
touch1["pageY"]+(a/2)
|
|
23048
23113
|
]
|
|
23049
23114
|
startTouchDistance = Math.sqrt((a*a)+(b*b));
|
|
23115
|
+
|
|
23116
|
+
// Store initial scale for ratio-based zoom calculation
|
|
23117
|
+
gesture = {
|
|
23118
|
+
initialScale: scaleFactor,
|
|
23119
|
+
initialDistance: startTouchDistance,
|
|
23120
|
+
mode: null // Will be determined on first significant move
|
|
23121
|
+
};
|
|
23122
|
+
|
|
23123
|
+
// Start gesture with fixed focal point (store in workspace coordinates)
|
|
23124
|
+
var focalPoint = [
|
|
23125
|
+
(touch0["pageX"] + touch1["pageX"]) / 2 - offset.left,
|
|
23126
|
+
(touch0["pageY"] + touch1["pageY"]) / 2 - offset.top
|
|
23127
|
+
];
|
|
23128
|
+
RED.view.zoomAnimator.startGesture(focalPoint, scaleFactor, scrollPos, scaleFactor);
|
|
23050
23129
|
} else {
|
|
23051
23130
|
var obj = d3.select(document.body);
|
|
23052
23131
|
touch0 = d3.event.touches.item(0);
|
|
@@ -23096,33 +23175,93 @@ RED.view = (function() {
|
|
|
23096
23175
|
var offset = chart.offset();
|
|
23097
23176
|
var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
|
|
23098
23177
|
var moveTouchDistance = Math.sqrt((a*a)+(b*b));
|
|
23099
|
-
|
|
23100
|
-
|
|
23101
|
-
|
|
23178
|
+
|
|
23179
|
+
// Calculate center point of two fingers
|
|
23180
|
+
var currentTouchCenter = [
|
|
23181
|
+
(touch0["pageX"] + touch1["pageX"]) / 2,
|
|
23182
|
+
(touch0["pageY"] + touch1["pageY"]) / 2
|
|
23102
23183
|
];
|
|
23103
23184
|
|
|
23104
23185
|
if (!isNaN(moveTouchDistance)) {
|
|
23105
|
-
|
|
23106
|
-
|
|
23107
|
-
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23111
|
-
|
|
23112
|
-
|
|
23113
|
-
|
|
23114
|
-
|
|
23186
|
+
// Determine gesture mode on first significant movement
|
|
23187
|
+
if (!gesture.mode) {
|
|
23188
|
+
var distanceChange = Math.abs(moveTouchDistance - startTouchDistance);
|
|
23189
|
+
var centerChange = moveTouchCenter ?
|
|
23190
|
+
Math.sqrt(Math.pow(currentTouchCenter[0] - moveTouchCenter[0], 2) +
|
|
23191
|
+
Math.pow(currentTouchCenter[1] - moveTouchCenter[1], 2)) : 0;
|
|
23192
|
+
|
|
23193
|
+
// Lock into zoom mode if distance changes significantly (>10px)
|
|
23194
|
+
// Lock into pan mode if center moves significantly (>5px) without distance change
|
|
23195
|
+
if (distanceChange > 10) {
|
|
23196
|
+
gesture.mode = 'zoom';
|
|
23197
|
+
} else if (centerChange > 5) {
|
|
23198
|
+
gesture.mode = 'pan';
|
|
23199
|
+
}
|
|
23200
|
+
}
|
|
23201
|
+
|
|
23202
|
+
// Once mode is determined, stay in that mode for the entire gesture
|
|
23203
|
+
if (gesture.mode === 'zoom') {
|
|
23204
|
+
oldScaleFactor = scaleFactor;
|
|
23205
|
+
// Use smooth ratio-based scaling for natural pinch-to-zoom
|
|
23206
|
+
var zoomRatio = moveTouchDistance / startTouchDistance;
|
|
23207
|
+
var minZoom = calculateMinZoom();
|
|
23208
|
+
var newScaleFactor = Math.min(RED.view.zoomConstants.MAX_ZOOM,
|
|
23209
|
+
Math.max(minZoom, gesture.initialScale * zoomRatio));
|
|
23210
|
+
|
|
23211
|
+
// Use gesture state management to maintain fixed focal point
|
|
23212
|
+
var gestureState = RED.view.zoomAnimator.updateGesture(newScaleFactor);
|
|
23213
|
+
|
|
23214
|
+
// Only call zoomView if scale is actually changing (not at limits)
|
|
23215
|
+
if (Math.abs(scaleFactor - newScaleFactor) >= 0.001) {
|
|
23216
|
+
// Get focal point converted back to current screen coordinates
|
|
23217
|
+
var currentScrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
23218
|
+
var focalPoint = RED.view.zoomAnimator.getGestureFocalPoint(currentScrollPos, scaleFactor);
|
|
23219
|
+
|
|
23220
|
+
if (focalPoint) {
|
|
23221
|
+
// Use the fixed focal point from gesture start (converted from workspace coords)
|
|
23222
|
+
zoomView(newScaleFactor, focalPoint);
|
|
23223
|
+
} else {
|
|
23224
|
+
// Fallback to current behavior if gesture not active
|
|
23225
|
+
var touchCenter = [
|
|
23226
|
+
touch1["pageX"]+(b/2),
|
|
23227
|
+
touch1["pageY"]+(a/2)
|
|
23228
|
+
];
|
|
23229
|
+
var pinchCenter = [
|
|
23230
|
+
touchCenter[0] - offset.left,
|
|
23231
|
+
touchCenter[1] - offset.top
|
|
23232
|
+
];
|
|
23233
|
+
zoomView(newScaleFactor, pinchCenter);
|
|
23234
|
+
}
|
|
23235
|
+
}
|
|
23236
|
+
} else if (gesture.mode === 'pan' || !gesture.mode) {
|
|
23237
|
+
// Two-finger pan: allow immediate panning even if mode not determined
|
|
23238
|
+
// Clear touchStartTime to prevent issues with next gesture
|
|
23239
|
+
if (touchStartTime) {
|
|
23240
|
+
clearTimeout(touchStartTime);
|
|
23241
|
+
touchStartTime = null;
|
|
23242
|
+
}
|
|
23243
|
+
if (moveTouchCenter) {
|
|
23244
|
+
var dx = currentTouchCenter[0] - moveTouchCenter[0];
|
|
23245
|
+
var dy = currentTouchCenter[1] - moveTouchCenter[1];
|
|
23246
|
+
|
|
23247
|
+
// Pan the canvas
|
|
23248
|
+
var currentScroll = [chart.scrollLeft(), chart.scrollTop()];
|
|
23249
|
+
chart.scrollLeft(currentScroll[0] - dx);
|
|
23250
|
+
chart.scrollTop(currentScroll[1] - dy);
|
|
23251
|
+
RED.events.emit("view:navigate");
|
|
23252
|
+
}
|
|
23253
|
+
// Update the center for next move
|
|
23254
|
+
moveTouchCenter = currentTouchCenter;
|
|
23255
|
+
}
|
|
23115
23256
|
|
|
23116
|
-
|
|
23117
|
-
chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]);
|
|
23118
|
-
redraw();
|
|
23257
|
+
// Don't update startTouchDistance - keep initial distance for ratio calculation
|
|
23119
23258
|
}
|
|
23120
23259
|
}
|
|
23121
23260
|
d3.event.preventDefault();
|
|
23122
23261
|
});
|
|
23123
|
-
|
|
23124
|
-
|
|
23125
|
-
|
|
23262
|
+
|
|
23263
|
+
const handleChartKeyboardEvents = (event) => {
|
|
23264
|
+
// Handle Alt toggle for pulling nodes out of groups
|
|
23126
23265
|
if (mouse_mode === RED.state.MOVING_ACTIVE && event.key === 'Alt' && groupAddParentGroup) {
|
|
23127
23266
|
RED.nodes.group(groupAddParentGroup).dirty = true
|
|
23128
23267
|
for (let n = 0; n<movingSet.length(); n++) {
|
|
@@ -23141,10 +23280,67 @@ RED.view = (function() {
|
|
|
23141
23280
|
}
|
|
23142
23281
|
}
|
|
23143
23282
|
RED.view.redraw()
|
|
23283
|
+
} else if (event.keyCode === 32 || event.key === ' ') {
|
|
23284
|
+
if (mouse_mode === RED.state.PANNING) {
|
|
23285
|
+
// Already in panning mode - just prevent the event default handler
|
|
23286
|
+
event.preventDefault()
|
|
23287
|
+
event.stopPropagation()
|
|
23288
|
+
if (event.type === 'keyup' && spacebarPressed) {
|
|
23289
|
+
spacebarPressed = false
|
|
23290
|
+
}
|
|
23291
|
+
} else if (mouse_mode === RED.state.DEFAULT) {
|
|
23292
|
+
// Handle spacebar for panning
|
|
23293
|
+
event.preventDefault();
|
|
23294
|
+
event.stopPropagation();
|
|
23295
|
+
if (event.type === "keydown" && !spacebarPressed) {
|
|
23296
|
+
spacebarPressed = true;
|
|
23297
|
+
// Change cursor to grab hand when spacebar is pressed
|
|
23298
|
+
outer.style('cursor', 'grab');
|
|
23299
|
+
} else if (event.type === "keyup" && spacebarPressed) {
|
|
23300
|
+
spacebarPressed = false;
|
|
23301
|
+
// Revert cursor when spacebar is released
|
|
23302
|
+
outer.style('cursor', '');
|
|
23303
|
+
}
|
|
23304
|
+
}
|
|
23144
23305
|
}
|
|
23145
23306
|
}
|
|
23146
|
-
|
|
23147
|
-
|
|
23307
|
+
chart.on("keydown", handleChartKeyboardEvents)
|
|
23308
|
+
chart.on("keyup", handleChartKeyboardEvents)
|
|
23309
|
+
|
|
23310
|
+
// // // Window-level keyup listener to catch spacebar release when cursor is outside canvas
|
|
23311
|
+
// // function handleWindowSpacebarUp(e) {
|
|
23312
|
+
// // if ((e.keyCode === 32 || e.key === ' ') && spacebarPressed) {
|
|
23313
|
+
// // spacebarPressed = false;
|
|
23314
|
+
// // // Revert cursor when spacebar is released outside canvas
|
|
23315
|
+
// // outer.style('cursor', '');
|
|
23316
|
+
// // e.preventDefault();
|
|
23317
|
+
// // e.stopPropagation();
|
|
23318
|
+
// // }
|
|
23319
|
+
// // }
|
|
23320
|
+
// // chart.on("keyup", handleSpacebarToggle)
|
|
23321
|
+
// // chart.on("keydown", handleSpacebarToggle)
|
|
23322
|
+
// // Additional window-level keyup listener to ensure spacebar state is cleared
|
|
23323
|
+
// // when cursor leaves canvas area while spacebar is held
|
|
23324
|
+
// window.addEventListener("keyup", handleWindowSpacebarUp)
|
|
23325
|
+
|
|
23326
|
+
// // Reset spacebar state when window loses focus to prevent stuck state
|
|
23327
|
+
// window.addEventListener("blur", function() {
|
|
23328
|
+
// if (spacebarPressed) {
|
|
23329
|
+
// spacebarPressed = false;
|
|
23330
|
+
// // Revert cursor when window loses focus
|
|
23331
|
+
// outer.style('cursor', '');
|
|
23332
|
+
// }
|
|
23333
|
+
// })
|
|
23334
|
+
|
|
23335
|
+
// Recalculate minimum zoom when window resizes
|
|
23336
|
+
$(window).on("resize.red-ui-view", function() {
|
|
23337
|
+
// Recalculate minimum zoom to ensure canvas fits in viewport
|
|
23338
|
+
var newMinZoom = calculateMinZoom();
|
|
23339
|
+
// If current zoom is below new minimum, adjust it
|
|
23340
|
+
if (scaleFactor < newMinZoom) {
|
|
23341
|
+
zoomView(newMinZoom);
|
|
23342
|
+
}
|
|
23343
|
+
})
|
|
23148
23344
|
|
|
23149
23345
|
// Workspace Background
|
|
23150
23346
|
eventLayer.append("svg:rect")
|
|
@@ -23236,22 +23432,194 @@ RED.view = (function() {
|
|
|
23236
23432
|
'<button class="red-ui-footer-button" id="red-ui-view-zoom-out"><i class="fa fa-minus"></i></button>'+
|
|
23237
23433
|
'<button class="red-ui-footer-button" id="red-ui-view-zoom-zero"><i class="fa fa-circle-o"></i></button>'+
|
|
23238
23434
|
'<button class="red-ui-footer-button" id="red-ui-view-zoom-in"><i class="fa fa-plus"></i></button>'+
|
|
23435
|
+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-fit"><i class="fa fa-compress"></i></button>'+
|
|
23239
23436
|
'</span>')
|
|
23240
23437
|
})
|
|
23241
23438
|
|
|
23242
|
-
$("#red-ui-view-zoom-out").on("click", zoomOut);
|
|
23439
|
+
$("#red-ui-view-zoom-out").on("click", function() { zoomOut(); });
|
|
23243
23440
|
RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out');
|
|
23244
23441
|
$("#red-ui-view-zoom-zero").on("click", zoomZero);
|
|
23245
23442
|
RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset');
|
|
23246
|
-
$("#red-ui-view-zoom-in").on("click", zoomIn);
|
|
23443
|
+
$("#red-ui-view-zoom-in").on("click", function() { zoomIn(); });
|
|
23247
23444
|
RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in');
|
|
23248
|
-
|
|
23249
|
-
|
|
23445
|
+
$("#red-ui-view-zoom-fit").on("click", zoomToFitAll);
|
|
23446
|
+
RED.popover.tooltip($("#red-ui-view-zoom-fit"),RED._('actions.zoom-fit'),'core:zoom-fit');
|
|
23447
|
+
// Legacy mouse wheel handler - disabled in favor of modern wheel event
|
|
23448
|
+
// chart.on("DOMMouseScroll mousewheel", function (evt) {
|
|
23449
|
+
// if ( evt.altKey || spacebarPressed ) {
|
|
23450
|
+
// evt.preventDefault();
|
|
23451
|
+
// evt.stopPropagation();
|
|
23452
|
+
// // Get cursor position relative to the chart
|
|
23453
|
+
// var offset = chart.offset();
|
|
23454
|
+
// var cursorPos = [
|
|
23455
|
+
// evt.originalEvent.pageX - offset.left,
|
|
23456
|
+
// evt.originalEvent.pageY - offset.top
|
|
23457
|
+
// ];
|
|
23458
|
+
// var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta;
|
|
23459
|
+
// if (move <= 0) { zoomOut(cursorPos); }
|
|
23460
|
+
// else { zoomIn(cursorPos); }
|
|
23461
|
+
// }
|
|
23462
|
+
// });
|
|
23463
|
+
|
|
23464
|
+
// Modern wheel event handler for better trackpad support (pinch-to-zoom) and momentum
|
|
23465
|
+
var momentumTimer = null;
|
|
23466
|
+
var trackpadGestureTimer = null;
|
|
23467
|
+
var lastWheelEventTime = 0;
|
|
23468
|
+
var wheelEventContinuityThreshold = 100; // Events within 100ms are same gesture
|
|
23469
|
+
var gestureEndThreshold = 500; // 500ms+ gap means gesture ended
|
|
23470
|
+
|
|
23471
|
+
// Prevent browser zoom on non-canvas areas
|
|
23472
|
+
document.addEventListener("wheel", function(e) {
|
|
23473
|
+
if (e.ctrlKey && !e.target.closest('#red-ui-workspace-chart')) {
|
|
23474
|
+
e.preventDefault();
|
|
23475
|
+
}
|
|
23476
|
+
}, { passive: false });
|
|
23477
|
+
|
|
23478
|
+
chart.on("wheel", function(evt) {
|
|
23479
|
+
if (mouse_mode === RED.state.PANNING) {
|
|
23480
|
+
// Ignore wheel events while panning
|
|
23481
|
+
return;
|
|
23482
|
+
}
|
|
23483
|
+
// ctrlKey is set during pinch gestures on trackpads
|
|
23484
|
+
if (evt.ctrlKey || evt.altKey || spacebarPressed) {
|
|
23485
|
+
evt.preventDefault();
|
|
23486
|
+
evt.stopPropagation();
|
|
23487
|
+
|
|
23488
|
+
var currentTime = Date.now();
|
|
23489
|
+
var timeSinceLastEvent = currentTime - lastWheelEventTime;
|
|
23490
|
+
|
|
23491
|
+
// Get cursor position relative to the chart
|
|
23492
|
+
var offset = chart.offset();
|
|
23493
|
+
var cursorPos = [
|
|
23494
|
+
evt.originalEvent.pageX - offset.left,
|
|
23495
|
+
evt.originalEvent.pageY - offset.top
|
|
23496
|
+
];
|
|
23497
|
+
var delta = evt.originalEvent.deltaY;
|
|
23498
|
+
|
|
23499
|
+
// For trackpad pinch (Ctrl+wheel), use smooth proportional zoom
|
|
23500
|
+
if (evt.ctrlKey && !evt.altKey && !spacebarPressed) {
|
|
23501
|
+
// Detect input device: trackpad has small deltas, mouse wheel has large deltas
|
|
23502
|
+
var isTrackpadInput = Math.abs(delta) < 50;
|
|
23503
|
+
// Invert delta: spreading fingers (negative deltaY) should zoom in
|
|
23504
|
+
var scaleDelta = RED.view.zoomAnimator.calculateZoomDelta(scaleFactor, -delta, isTrackpadInput);
|
|
23505
|
+
var minZoom = calculateMinZoom();
|
|
23506
|
+
var newScale = Math.min(RED.view.zoomConstants.MAX_ZOOM,
|
|
23507
|
+
Math.max(minZoom, scaleFactor + scaleDelta));
|
|
23508
|
+
|
|
23509
|
+
// Session-based gesture tracking:
|
|
23510
|
+
// - If no active gesture OR gap > gestureEndThreshold, start new gesture
|
|
23511
|
+
// - If gap < wheelEventContinuityThreshold, continue current gesture
|
|
23512
|
+
// - If gap between continuity and end threshold, keep current gesture but don't update focal point
|
|
23513
|
+
|
|
23514
|
+
if (!RED.view.zoomAnimator.isGestureActive() || timeSinceLastEvent > gestureEndThreshold) {
|
|
23515
|
+
// Start new gesture session - store focal point in workspace coordinates
|
|
23516
|
+
var scrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
23517
|
+
RED.view.zoomAnimator.startGesture(cursorPos, scaleFactor, scrollPos, scaleFactor);
|
|
23518
|
+
} else if (timeSinceLastEvent <= wheelEventContinuityThreshold) {
|
|
23519
|
+
// Events are continuous - this is the same gesture, focal point remains locked
|
|
23520
|
+
// No need to update focal point
|
|
23521
|
+
}
|
|
23522
|
+
// For gaps between continuity and end threshold, keep existing gesture state
|
|
23523
|
+
|
|
23524
|
+
// Update gesture with new scale, maintaining locked focal point
|
|
23525
|
+
RED.view.zoomAnimator.updateGesture(newScale);
|
|
23526
|
+
// Only call zoomView if scale is actually changing (not at limits)
|
|
23527
|
+
if (Math.abs(scaleFactor - newScale) >= 0.001) {
|
|
23528
|
+
// Get focal point converted back to current screen coordinates
|
|
23529
|
+
var currentScrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
23530
|
+
var focalPoint = RED.view.zoomAnimator.getGestureFocalPoint(currentScrollPos, scaleFactor);
|
|
23531
|
+
zoomView(newScale, focalPoint); // Direct call, no animation
|
|
23532
|
+
}
|
|
23533
|
+
|
|
23534
|
+
// Update last event time for continuity tracking
|
|
23535
|
+
lastWheelEventTime = currentTime;
|
|
23536
|
+
|
|
23537
|
+
// Reset gesture timeout - end gesture when no more events come in for gestureEndThreshold
|
|
23538
|
+
if (trackpadGestureTimer) {
|
|
23539
|
+
clearTimeout(trackpadGestureTimer);
|
|
23540
|
+
}
|
|
23541
|
+
trackpadGestureTimer = setTimeout(function() {
|
|
23542
|
+
RED.view.zoomAnimator.endGesture();
|
|
23543
|
+
trackpadGestureTimer = null;
|
|
23544
|
+
// Store zoom level when gesture completes
|
|
23545
|
+
if (RED.settings.get("editor.view.view-store-zoom")) {
|
|
23546
|
+
RED.settings.setLocal('zoom-level', scaleFactor.toFixed(1));
|
|
23547
|
+
}
|
|
23548
|
+
}, gestureEndThreshold); // Use 500ms timeout for gesture end detection
|
|
23549
|
+
} else {
|
|
23550
|
+
// Regular Alt+scroll or Space+scroll - use smooth zoom without animation
|
|
23551
|
+
// Detect input device: trackpad has small deltas, mouse wheel has large deltas
|
|
23552
|
+
var isTrackpadInput = Math.abs(delta) < 50;
|
|
23553
|
+
var scaleDelta = RED.view.zoomAnimator.calculateZoomDelta(scaleFactor, -delta, isTrackpadInput);
|
|
23554
|
+
var minZoom = calculateMinZoom();
|
|
23555
|
+
var newScale = Math.min(RED.view.zoomConstants.MAX_ZOOM,
|
|
23556
|
+
Math.max(minZoom, scaleFactor + scaleDelta));
|
|
23557
|
+
|
|
23558
|
+
// Use gesture tracking for stable focal point like trackpad pinch
|
|
23559
|
+
if (!RED.view.zoomAnimator.isGestureActive() || timeSinceLastEvent > gestureEndThreshold) {
|
|
23560
|
+
// Start new gesture session - store focal point in workspace coordinates
|
|
23561
|
+
var scrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
23562
|
+
RED.view.zoomAnimator.startGesture(cursorPos, scaleFactor, scrollPos, scaleFactor);
|
|
23563
|
+
} else if (timeSinceLastEvent <= wheelEventContinuityThreshold) {
|
|
23564
|
+
// Events are continuous - same gesture, focal point remains locked
|
|
23565
|
+
}
|
|
23566
|
+
|
|
23567
|
+
// Update gesture with new scale, maintaining locked focal point
|
|
23568
|
+
RED.view.zoomAnimator.updateGesture(newScale);
|
|
23569
|
+
|
|
23570
|
+
// Only zoom if scale is actually changing
|
|
23571
|
+
if (Math.abs(scaleFactor - newScale) >= 0.001) {
|
|
23572
|
+
// Get focal point converted back to current screen coordinates
|
|
23573
|
+
var currentScrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
23574
|
+
var focalPoint = RED.view.zoomAnimator.getGestureFocalPoint(currentScrollPos, scaleFactor);
|
|
23575
|
+
zoomView(newScale, focalPoint);
|
|
23576
|
+
}
|
|
23577
|
+
|
|
23578
|
+
// Update last event time for continuity tracking
|
|
23579
|
+
lastWheelEventTime = currentTime;
|
|
23580
|
+
|
|
23581
|
+
// Reset gesture timeout
|
|
23582
|
+
if (trackpadGestureTimer) {
|
|
23583
|
+
clearTimeout(trackpadGestureTimer);
|
|
23584
|
+
}
|
|
23585
|
+
trackpadGestureTimer = setTimeout(function() {
|
|
23586
|
+
RED.view.zoomAnimator.endGesture();
|
|
23587
|
+
trackpadGestureTimer = null;
|
|
23588
|
+
// Store zoom level when gesture completes
|
|
23589
|
+
if (RED.settings.get("editor.view.view-store-zoom")) {
|
|
23590
|
+
RED.settings.setLocal('zoom-level', scaleFactor.toFixed(1));
|
|
23591
|
+
}
|
|
23592
|
+
}, gestureEndThreshold);
|
|
23593
|
+
}
|
|
23594
|
+
} else {
|
|
23595
|
+
// Regular scroll - prevent default and manually handle both axes
|
|
23250
23596
|
evt.preventDefault();
|
|
23251
23597
|
evt.stopPropagation();
|
|
23252
|
-
|
|
23253
|
-
|
|
23254
|
-
|
|
23598
|
+
|
|
23599
|
+
// Apply scroll deltas directly to both axes
|
|
23600
|
+
var deltaX = evt.originalEvent.deltaX;
|
|
23601
|
+
var deltaY = evt.originalEvent.deltaY;
|
|
23602
|
+
|
|
23603
|
+
chart.scrollLeft(chart.scrollLeft() + deltaX);
|
|
23604
|
+
chart.scrollTop(chart.scrollTop() + deltaY);
|
|
23605
|
+
|
|
23606
|
+
// Emit navigate event for minimap
|
|
23607
|
+
RED.events.emit("view:navigate");
|
|
23608
|
+
|
|
23609
|
+
// Track velocity and apply momentum
|
|
23610
|
+
handleScroll();
|
|
23611
|
+
|
|
23612
|
+
// Cancel previous momentum timer
|
|
23613
|
+
if (momentumTimer) {
|
|
23614
|
+
clearTimeout(momentumTimer);
|
|
23615
|
+
}
|
|
23616
|
+
|
|
23617
|
+
// Start momentum after scroll stops
|
|
23618
|
+
momentumTimer = setTimeout(function() {
|
|
23619
|
+
if (Math.abs(scrollVelocity.x) > MIN_VELOCITY || Math.abs(scrollVelocity.y) > MIN_VELOCITY) {
|
|
23620
|
+
startMomentumScroll();
|
|
23621
|
+
}
|
|
23622
|
+
}, 100);
|
|
23255
23623
|
}
|
|
23256
23624
|
});
|
|
23257
23625
|
|
|
@@ -23422,6 +23790,12 @@ RED.view = (function() {
|
|
|
23422
23790
|
});
|
|
23423
23791
|
chart.on("blur", function() {
|
|
23424
23792
|
$("#red-ui-workspace-tabs").removeClass("red-ui-workspace-focussed");
|
|
23793
|
+
// Reset spacebar state when chart loses focus to prevent stuck state
|
|
23794
|
+
if (spacebarPressed) {
|
|
23795
|
+
spacebarPressed = false;
|
|
23796
|
+
// Revert cursor when chart loses focus
|
|
23797
|
+
outer.style('cursor', '');
|
|
23798
|
+
}
|
|
23425
23799
|
});
|
|
23426
23800
|
|
|
23427
23801
|
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
|
@@ -23486,6 +23860,7 @@ RED.view = (function() {
|
|
|
23486
23860
|
RED.actions.add("core:zoom-in",zoomIn);
|
|
23487
23861
|
RED.actions.add("core:zoom-out",zoomOut);
|
|
23488
23862
|
RED.actions.add("core:zoom-reset",zoomZero);
|
|
23863
|
+
RED.actions.add("core:zoom-fit",zoomToFitAll);
|
|
23489
23864
|
RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)});
|
|
23490
23865
|
RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)});
|
|
23491
23866
|
|
|
@@ -23601,6 +23976,9 @@ RED.view = (function() {
|
|
|
23601
23976
|
RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) )
|
|
23602
23977
|
}
|
|
23603
23978
|
chart.on("scroll", function() {
|
|
23979
|
+
// Track scroll velocity for momentum
|
|
23980
|
+
handleScroll();
|
|
23981
|
+
|
|
23604
23982
|
if (RED.settings.get("editor.view.view-store-position")) {
|
|
23605
23983
|
if (onScrollTimer) {
|
|
23606
23984
|
clearTimeout(onScrollTimer)
|
|
@@ -23878,12 +24256,26 @@ RED.view = (function() {
|
|
|
23878
24256
|
return;
|
|
23879
24257
|
}
|
|
23880
24258
|
|
|
24259
|
+
// Spacebar + left click for panning
|
|
24260
|
+
if (spacebarPressed && d3.event.button === 0) {
|
|
24261
|
+
d3.event.preventDefault();
|
|
24262
|
+
d3.event.stopPropagation();
|
|
24263
|
+
mouse_mode = RED.state.PANNING;
|
|
24264
|
+
mouse_position = [d3.event.pageX,d3.event.pageY]
|
|
24265
|
+
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
|
|
24266
|
+
// Change cursor to grabbing while actively panning
|
|
24267
|
+
outer.style('cursor', 'grabbing');
|
|
24268
|
+
return;
|
|
24269
|
+
}
|
|
24270
|
+
|
|
23881
24271
|
if (d3.event.button === 1) {
|
|
23882
24272
|
// Middle Click pan
|
|
23883
24273
|
d3.event.preventDefault();
|
|
23884
24274
|
mouse_mode = RED.state.PANNING;
|
|
23885
24275
|
mouse_position = [d3.event.pageX,d3.event.pageY]
|
|
23886
24276
|
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
|
|
24277
|
+
// Change cursor to grabbing while actively panning
|
|
24278
|
+
outer.style('cursor', 'grabbing');
|
|
23887
24279
|
return;
|
|
23888
24280
|
}
|
|
23889
24281
|
if (d3.event.button === 2) {
|
|
@@ -24415,6 +24807,30 @@ RED.view = (function() {
|
|
|
24415
24807
|
redraw();
|
|
24416
24808
|
}
|
|
24417
24809
|
|
|
24810
|
+
function startPanning () {
|
|
24811
|
+
|
|
24812
|
+
}
|
|
24813
|
+
window.addEventListener('mousemove', windowMouseMove)
|
|
24814
|
+
window.addEventListener('touchmove', windowMouseMove)
|
|
24815
|
+
|
|
24816
|
+
function windowMouseMove (event) {
|
|
24817
|
+
if (mouse_mode === RED.state.PANNING) {
|
|
24818
|
+
let pos = [event.pageX, event.pageY]
|
|
24819
|
+
if (event.touches) {
|
|
24820
|
+
let touch0 = event.touches.item(0)
|
|
24821
|
+
pos = [touch0.pageX, touch0.pageY]
|
|
24822
|
+
}
|
|
24823
|
+
const deltaPos = [
|
|
24824
|
+
mouse_position[0]-pos[0],
|
|
24825
|
+
mouse_position[1]-pos[1]
|
|
24826
|
+
]
|
|
24827
|
+
chart.scrollLeft(scroll_position[0]+deltaPos[0])
|
|
24828
|
+
chart.scrollTop(scroll_position[1]+deltaPos[1])
|
|
24829
|
+
RED.events.emit("view:navigate");
|
|
24830
|
+
return
|
|
24831
|
+
}
|
|
24832
|
+
}
|
|
24833
|
+
|
|
24418
24834
|
function canvasMouseMove() {
|
|
24419
24835
|
var i;
|
|
24420
24836
|
var node;
|
|
@@ -24429,18 +24845,8 @@ RED.view = (function() {
|
|
|
24429
24845
|
//console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
|
|
24430
24846
|
|
|
24431
24847
|
if (mouse_mode === RED.state.PANNING) {
|
|
24432
|
-
|
|
24433
|
-
|
|
24434
|
-
var touch0 = d3.event.touches.item(0);
|
|
24435
|
-
pos = [touch0.pageX, touch0.pageY];
|
|
24436
|
-
}
|
|
24437
|
-
var deltaPos = [
|
|
24438
|
-
mouse_position[0]-pos[0],
|
|
24439
|
-
mouse_position[1]-pos[1]
|
|
24440
|
-
];
|
|
24441
|
-
|
|
24442
|
-
chart.scrollLeft(scroll_position[0]+deltaPos[0])
|
|
24443
|
-
chart.scrollTop(scroll_position[1]+deltaPos[1])
|
|
24848
|
+
// A window-level handler is used for panning so the mouse can leave the confines of the chart
|
|
24849
|
+
// but continue panning
|
|
24444
24850
|
return
|
|
24445
24851
|
}
|
|
24446
24852
|
|
|
@@ -24784,6 +25190,12 @@ RED.view = (function() {
|
|
|
24784
25190
|
}
|
|
24785
25191
|
if (mouse_mode === RED.state.PANNING) {
|
|
24786
25192
|
resetMouseVars();
|
|
25193
|
+
// Revert to grab cursor if spacebar still held, otherwise clear cursor
|
|
25194
|
+
if (spacebarPressed) {
|
|
25195
|
+
outer.style('cursor', 'grab');
|
|
25196
|
+
} else {
|
|
25197
|
+
outer.style('cursor', '');
|
|
25198
|
+
}
|
|
24787
25199
|
return
|
|
24788
25200
|
}
|
|
24789
25201
|
if (mouse_mode === RED.state.SELECTING_NODE) {
|
|
@@ -25115,39 +25527,454 @@ RED.view = (function() {
|
|
|
25115
25527
|
|
|
25116
25528
|
}
|
|
25117
25529
|
|
|
25118
|
-
function
|
|
25119
|
-
|
|
25120
|
-
|
|
25530
|
+
function calculateMinZoom() {
|
|
25531
|
+
// Calculate the minimum zoom to ensure canvas always fills the viewport (no empty space)
|
|
25532
|
+
var viewportWidth = chart.width();
|
|
25533
|
+
var viewportHeight = chart.height();
|
|
25534
|
+
|
|
25535
|
+
// Canvas is 8000x8000, calculate zoom to cover viewport
|
|
25536
|
+
var zoomToFitWidth = viewportWidth / space_width;
|
|
25537
|
+
var zoomToFitHeight = viewportHeight / space_height;
|
|
25538
|
+
|
|
25539
|
+
// Use the LARGER zoom to ensure canvas covers entire viewport (no empty space visible)
|
|
25540
|
+
var calculatedMinZoom = Math.max(zoomToFitWidth, zoomToFitHeight);
|
|
25541
|
+
|
|
25542
|
+
// Return the larger of the calculated min or the configured min
|
|
25543
|
+
// This ensures canvas always fills the viewport
|
|
25544
|
+
return Math.max(calculatedMinZoom, RED.view.zoomConstants.MIN_ZOOM);
|
|
25545
|
+
}
|
|
25546
|
+
|
|
25547
|
+
// Track focal point for sequential button/hotkey zoom operations
|
|
25548
|
+
// Store in workspace coordinates so it remains valid after viewport shifts
|
|
25549
|
+
var buttonZoomWorkspaceCenter = null;
|
|
25550
|
+
var buttonZoomTimeout = null;
|
|
25551
|
+
var BUTTON_ZOOM_FOCAL_TIMEOUT = 1000; // ms - time to keep same focal point
|
|
25552
|
+
|
|
25553
|
+
function zoomIn(focalPoint) {
|
|
25554
|
+
if (scaleFactor < RED.view.zoomConstants.MAX_ZOOM) {
|
|
25555
|
+
var useFocalPoint = null;
|
|
25556
|
+
|
|
25557
|
+
// If focalPoint is explicitly provided (e.g., from wheel/pinch), use it directly
|
|
25558
|
+
if (focalPoint) {
|
|
25559
|
+
useFocalPoint = focalPoint;
|
|
25560
|
+
} else {
|
|
25561
|
+
// For button/hotkey zoom, maintain the same workspace center across sequential zooms
|
|
25562
|
+
if (!buttonZoomWorkspaceCenter) {
|
|
25563
|
+
// First button zoom - calculate and store workspace center
|
|
25564
|
+
var screenSize = [chart.width(), chart.height()];
|
|
25565
|
+
var scrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
25566
|
+
// Convert viewport center to workspace coordinates
|
|
25567
|
+
buttonZoomWorkspaceCenter = [
|
|
25568
|
+
(scrollPos[0] + screenSize[0]/2) / scaleFactor,
|
|
25569
|
+
(scrollPos[1] + screenSize[1]/2) / scaleFactor
|
|
25570
|
+
];
|
|
25571
|
+
}
|
|
25572
|
+
|
|
25573
|
+
// ALWAYS use viewport center as focal point (fixed screen position)
|
|
25574
|
+
// The stored workspace center will be kept at this screen position
|
|
25575
|
+
var screenSize = [chart.width(), chart.height()];
|
|
25576
|
+
useFocalPoint = [screenSize[0]/2, screenSize[1]/2];
|
|
25577
|
+
|
|
25578
|
+
// Reset timeout
|
|
25579
|
+
clearTimeout(buttonZoomTimeout);
|
|
25580
|
+
buttonZoomTimeout = setTimeout(function() {
|
|
25581
|
+
buttonZoomWorkspaceCenter = null;
|
|
25582
|
+
}, BUTTON_ZOOM_FOCAL_TIMEOUT);
|
|
25583
|
+
}
|
|
25584
|
+
|
|
25585
|
+
animatedZoomView(scaleFactor + RED.view.zoomConstants.ZOOM_STEP, useFocalPoint, buttonZoomWorkspaceCenter);
|
|
25586
|
+
}
|
|
25587
|
+
}
|
|
25588
|
+
function zoomOut(focalPoint) {
|
|
25589
|
+
var minZoom = calculateMinZoom();
|
|
25590
|
+
if (scaleFactor > minZoom) {
|
|
25591
|
+
var useFocalPoint = null;
|
|
25592
|
+
|
|
25593
|
+
if (focalPoint) {
|
|
25594
|
+
useFocalPoint = focalPoint;
|
|
25595
|
+
} else {
|
|
25596
|
+
if (!buttonZoomWorkspaceCenter) {
|
|
25597
|
+
var screenSize = [chart.width(), chart.height()];
|
|
25598
|
+
var scrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
25599
|
+
buttonZoomWorkspaceCenter = [
|
|
25600
|
+
(scrollPos[0] + screenSize[0]/2) / scaleFactor,
|
|
25601
|
+
(scrollPos[1] + screenSize[1]/2) / scaleFactor
|
|
25602
|
+
];
|
|
25603
|
+
}
|
|
25604
|
+
|
|
25605
|
+
// ALWAYS use viewport center as focal point (fixed screen position)
|
|
25606
|
+
var screenSize = [chart.width(), chart.height()];
|
|
25607
|
+
useFocalPoint = [screenSize[0]/2, screenSize[1]/2];
|
|
25608
|
+
|
|
25609
|
+
clearTimeout(buttonZoomTimeout);
|
|
25610
|
+
buttonZoomTimeout = setTimeout(function() {
|
|
25611
|
+
buttonZoomWorkspaceCenter = null;
|
|
25612
|
+
}, BUTTON_ZOOM_FOCAL_TIMEOUT);
|
|
25613
|
+
}
|
|
25614
|
+
|
|
25615
|
+
animatedZoomView(Math.max(scaleFactor - RED.view.zoomConstants.ZOOM_STEP, minZoom), useFocalPoint, buttonZoomWorkspaceCenter);
|
|
25121
25616
|
}
|
|
25122
25617
|
}
|
|
25123
|
-
function
|
|
25124
|
-
|
|
25125
|
-
|
|
25618
|
+
function zoomZero() {
|
|
25619
|
+
// Reset button zoom focal point for zoom reset
|
|
25620
|
+
clearTimeout(buttonZoomTimeout);
|
|
25621
|
+
buttonZoomWorkspaceCenter = null;
|
|
25622
|
+
animatedZoomView(1);
|
|
25623
|
+
}
|
|
25624
|
+
|
|
25625
|
+
function zoomToFitAll() {
|
|
25626
|
+
// Refresh active nodes to ensure we have the latest
|
|
25627
|
+
updateActiveNodes();
|
|
25628
|
+
|
|
25629
|
+
// Get all nodes in active workspace
|
|
25630
|
+
if (!activeNodes || activeNodes.length === 0) {
|
|
25631
|
+
return; // No nodes to fit
|
|
25632
|
+
}
|
|
25633
|
+
|
|
25634
|
+
// Calculate bounding box of all nodes
|
|
25635
|
+
var minX = Infinity, minY = Infinity;
|
|
25636
|
+
var maxX = -Infinity, maxY = -Infinity;
|
|
25637
|
+
|
|
25638
|
+
activeNodes.forEach(function(node) {
|
|
25639
|
+
var nodeLeft = node.x - node.w / 2;
|
|
25640
|
+
var nodeRight = node.x + node.w / 2;
|
|
25641
|
+
var nodeTop = node.y - node.h / 2;
|
|
25642
|
+
var nodeBottom = node.y + node.h / 2;
|
|
25643
|
+
|
|
25644
|
+
minX = Math.min(minX, nodeLeft);
|
|
25645
|
+
maxX = Math.max(maxX, nodeRight);
|
|
25646
|
+
minY = Math.min(minY, nodeTop);
|
|
25647
|
+
maxY = Math.max(maxY, nodeBottom);
|
|
25648
|
+
});
|
|
25649
|
+
|
|
25650
|
+
// Add padding around nodes for visual breathing room
|
|
25651
|
+
var padding = 80;
|
|
25652
|
+
minX -= padding;
|
|
25653
|
+
minY -= padding;
|
|
25654
|
+
maxX += padding;
|
|
25655
|
+
maxY += padding;
|
|
25656
|
+
|
|
25657
|
+
// Calculate dimensions of bounding box
|
|
25658
|
+
var boundingWidth = maxX - minX;
|
|
25659
|
+
var boundingHeight = maxY - minY;
|
|
25660
|
+
|
|
25661
|
+
// Get viewport dimensions
|
|
25662
|
+
var viewportWidth = chart.width();
|
|
25663
|
+
var viewportHeight = chart.height();
|
|
25664
|
+
|
|
25665
|
+
// Calculate zoom level that fits bounding box in viewport
|
|
25666
|
+
var zoomX = viewportWidth / boundingWidth;
|
|
25667
|
+
var zoomY = viewportHeight / boundingHeight;
|
|
25668
|
+
var targetZoom = Math.min(zoomX, zoomY);
|
|
25669
|
+
|
|
25670
|
+
// Respect minimum and maximum zoom limits
|
|
25671
|
+
var minZoom = calculateMinZoom();
|
|
25672
|
+
targetZoom = Math.max(minZoom, Math.min(RED.view.zoomConstants.MAX_ZOOM, targetZoom));
|
|
25673
|
+
|
|
25674
|
+
// Calculate center point of bounding box in workspace coordinates
|
|
25675
|
+
var centerX = (minX + maxX) / 2;
|
|
25676
|
+
var centerY = (minY + maxY) / 2;
|
|
25677
|
+
|
|
25678
|
+
// Reset button zoom focal point for zoom-to-fit
|
|
25679
|
+
clearTimeout(buttonZoomTimeout);
|
|
25680
|
+
buttonZoomWorkspaceCenter = null;
|
|
25681
|
+
|
|
25682
|
+
// Pass the bounding box center as workspace center
|
|
25683
|
+
// This ensures the nodes are centered in viewport after zoom
|
|
25684
|
+
var focalPoint = [viewportWidth / 2, viewportHeight / 2];
|
|
25685
|
+
|
|
25686
|
+
// If zoom level won't change significantly, animate just the pan
|
|
25687
|
+
if (Math.abs(scaleFactor - targetZoom) < 0.01) {
|
|
25688
|
+
var targetScrollLeft = centerX * scaleFactor - viewportWidth / 2;
|
|
25689
|
+
var targetScrollTop = centerY * scaleFactor - viewportHeight / 2;
|
|
25690
|
+
|
|
25691
|
+
// Calculate pan distance to determine duration (match zoom animation logic)
|
|
25692
|
+
var startScrollLeft = chart.scrollLeft();
|
|
25693
|
+
var startScrollTop = chart.scrollTop();
|
|
25694
|
+
var panDistance = Math.sqrt(
|
|
25695
|
+
Math.pow(targetScrollLeft - startScrollLeft, 2) +
|
|
25696
|
+
Math.pow(targetScrollTop - startScrollTop, 2)
|
|
25697
|
+
);
|
|
25698
|
+
|
|
25699
|
+
// Use similar duration calculation as zoom: scale with distance
|
|
25700
|
+
// Normalize by viewport diagonal for consistent feel
|
|
25701
|
+
var viewportDiagonal = Math.sqrt(viewportWidth * viewportWidth + viewportHeight * viewportHeight);
|
|
25702
|
+
var relativeDistance = panDistance / viewportDiagonal;
|
|
25703
|
+
// Duration scales with distance, matching zoom animation feel
|
|
25704
|
+
var duration = Math.max(200, Math.min(350, relativeDistance * RED.view.zoomConstants.DEFAULT_ZOOM_DURATION * 4));
|
|
25705
|
+
|
|
25706
|
+
RED.view.zoomAnimator.easeToValuesRAF({
|
|
25707
|
+
fromValues: {
|
|
25708
|
+
scrollLeft: startScrollLeft,
|
|
25709
|
+
scrollTop: startScrollTop
|
|
25710
|
+
},
|
|
25711
|
+
toValues: {
|
|
25712
|
+
scrollLeft: targetScrollLeft,
|
|
25713
|
+
scrollTop: targetScrollTop
|
|
25714
|
+
},
|
|
25715
|
+
duration: duration,
|
|
25716
|
+
onStep: function(values) {
|
|
25717
|
+
chart.scrollLeft(values.scrollLeft);
|
|
25718
|
+
chart.scrollTop(values.scrollTop);
|
|
25719
|
+
},
|
|
25720
|
+
onStart: function() {
|
|
25721
|
+
RED.events.emit("view:navigate");
|
|
25722
|
+
}
|
|
25723
|
+
});
|
|
25724
|
+
} else {
|
|
25725
|
+
animatedZoomView(targetZoom, focalPoint, [centerX, centerY]);
|
|
25126
25726
|
}
|
|
25127
25727
|
}
|
|
25128
|
-
|
|
25728
|
+
|
|
25129
25729
|
function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); }
|
|
25130
25730
|
function searchPrev() { RED.actions.invoke("core:search-previous"); }
|
|
25131
25731
|
function searchNext() { RED.actions.invoke("core:search-next"); }
|
|
25132
25732
|
|
|
25133
25733
|
|
|
25134
|
-
function zoomView(factor) {
|
|
25734
|
+
function zoomView(factor, focalPoint) {
|
|
25735
|
+
// Early return if scale factor isn't actually changing
|
|
25736
|
+
// This prevents focal point shifts when at zoom limits
|
|
25737
|
+
if (Math.abs(scaleFactor - factor) < 0.001) {
|
|
25738
|
+
return;
|
|
25739
|
+
}
|
|
25740
|
+
// Make scale 1 'sticky'
|
|
25741
|
+
if (Math.abs(1.0 - factor) < 0.02) {
|
|
25742
|
+
factor = 1
|
|
25743
|
+
}
|
|
25744
|
+
|
|
25745
|
+
console.log(factor)
|
|
25135
25746
|
var screenSize = [chart.width(),chart.height()];
|
|
25136
25747
|
var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
|
|
25137
|
-
var
|
|
25748
|
+
var oldScaleFactor = scaleFactor;
|
|
25749
|
+
|
|
25750
|
+
// Calculate workspace coordinates of the point that should remain fixed
|
|
25751
|
+
var center;
|
|
25752
|
+
if (focalPoint) {
|
|
25753
|
+
// focalPoint is in screen coordinates, convert to workspace coordinates
|
|
25754
|
+
center = [(scrollPos[0] + focalPoint[0])/oldScaleFactor, (scrollPos[1] + focalPoint[1])/oldScaleFactor];
|
|
25755
|
+
} else {
|
|
25756
|
+
// Default to viewport center in workspace coordinates
|
|
25757
|
+
center = [(scrollPos[0] + screenSize[0]/2)/oldScaleFactor, (scrollPos[1] + screenSize[1]/2)/oldScaleFactor];
|
|
25758
|
+
}
|
|
25759
|
+
|
|
25138
25760
|
scaleFactor = factor;
|
|
25139
|
-
|
|
25140
|
-
|
|
25141
|
-
|
|
25142
|
-
|
|
25761
|
+
|
|
25762
|
+
// Calculate new scroll position to keep the center point at the same screen position
|
|
25763
|
+
if (focalPoint) {
|
|
25764
|
+
// Keep the focal point at the same screen position
|
|
25765
|
+
chart.scrollLeft(center[0] * scaleFactor - focalPoint[0]);
|
|
25766
|
+
chart.scrollTop(center[1] * scaleFactor - focalPoint[1]);
|
|
25767
|
+
} else {
|
|
25768
|
+
// Keep viewport center on the same workspace coordinates
|
|
25769
|
+
var newScrollLeft = center[0] * scaleFactor - screenSize[0]/2;
|
|
25770
|
+
var newScrollTop = center[1] * scaleFactor - screenSize[1]/2;
|
|
25771
|
+
chart.scrollLeft(newScrollLeft);
|
|
25772
|
+
chart.scrollTop(newScrollTop);
|
|
25773
|
+
}
|
|
25143
25774
|
|
|
25144
25775
|
RED.view.navigator.resize();
|
|
25145
25776
|
redraw();
|
|
25777
|
+
RED.events.emit("view:navigate");
|
|
25146
25778
|
if (RED.settings.get("editor.view.view-store-zoom")) {
|
|
25147
25779
|
RED.settings.setLocal('zoom-level', factor.toFixed(1))
|
|
25148
25780
|
}
|
|
25149
25781
|
}
|
|
25150
25782
|
|
|
25783
|
+
function animatedZoomView(targetFactor, focalPoint, workspaceCenter) {
|
|
25784
|
+
// Cancel any in-progress animation
|
|
25785
|
+
if (cancelInProgressAnimation) {
|
|
25786
|
+
cancelInProgressAnimation();
|
|
25787
|
+
cancelInProgressAnimation = null;
|
|
25788
|
+
}
|
|
25789
|
+
|
|
25790
|
+
// Calculate the actual minimum zoom to fit canvas
|
|
25791
|
+
var minZoom = calculateMinZoom();
|
|
25792
|
+
|
|
25793
|
+
// Clamp target factor to valid range
|
|
25794
|
+
targetFactor = Math.max(minZoom,
|
|
25795
|
+
Math.min(RED.view.zoomConstants.MAX_ZOOM, targetFactor));
|
|
25796
|
+
|
|
25797
|
+
// If we're already at the target, no need to animate
|
|
25798
|
+
// Use a more tolerant threshold to account for floating-point precision
|
|
25799
|
+
if (Math.abs(scaleFactor - targetFactor) < 0.01) {
|
|
25800
|
+
return;
|
|
25801
|
+
}
|
|
25802
|
+
// Make scale 1 'sticky'
|
|
25803
|
+
if (Math.abs(1.0 - targetFactor) < 0.02) {
|
|
25804
|
+
targetFactor = 1
|
|
25805
|
+
}
|
|
25806
|
+
|
|
25807
|
+
|
|
25808
|
+
var startFactor = scaleFactor;
|
|
25809
|
+
var screenSize = [chart.width(), chart.height()];
|
|
25810
|
+
var scrollPos = [chart.scrollLeft(), chart.scrollTop()];
|
|
25811
|
+
|
|
25812
|
+
// Calculate the focal point in workspace coordinates (will remain constant)
|
|
25813
|
+
var center;
|
|
25814
|
+
if (workspaceCenter) {
|
|
25815
|
+
// Use the provided workspace center directly (for button zoom focal point locking)
|
|
25816
|
+
center = workspaceCenter;
|
|
25817
|
+
} else if (focalPoint) {
|
|
25818
|
+
// focalPoint is in screen coordinates, convert to workspace coordinates
|
|
25819
|
+
center = [(scrollPos[0] + focalPoint[0])/scaleFactor, (scrollPos[1] + focalPoint[1])/scaleFactor];
|
|
25820
|
+
} else {
|
|
25821
|
+
// Default to viewport center
|
|
25822
|
+
center = [(scrollPos[0] + screenSize[0]/2)/scaleFactor, (scrollPos[1] + screenSize[1]/2)/scaleFactor];
|
|
25823
|
+
}
|
|
25824
|
+
|
|
25825
|
+
// Calculate duration based on relative zoom change to maintain consistent velocity
|
|
25826
|
+
// Use logarithmic scaling since zoom feels exponential to the user
|
|
25827
|
+
var zoomRatio = targetFactor / startFactor;
|
|
25828
|
+
var logChange = Math.abs(Math.log(zoomRatio));
|
|
25829
|
+
// Scale duration more aggressively: multiply by 2 for stronger effect
|
|
25830
|
+
// At extreme zoom levels, animation will be noticeably longer
|
|
25831
|
+
var duration = Math.max(200, Math.min(350, logChange / 0.693 * RED.view.zoomConstants.DEFAULT_ZOOM_DURATION * 2));
|
|
25832
|
+
|
|
25833
|
+
// Start the animation
|
|
25834
|
+
cancelInProgressAnimation = RED.view.zoomAnimator.easeToValuesRAF({
|
|
25835
|
+
fromValues: {
|
|
25836
|
+
zoom: startFactor
|
|
25837
|
+
},
|
|
25838
|
+
toValues: {
|
|
25839
|
+
zoom: targetFactor
|
|
25840
|
+
},
|
|
25841
|
+
duration: duration,
|
|
25842
|
+
interpolateValue: true, // Use exponential interpolation for zoom
|
|
25843
|
+
onStep: function(values) {
|
|
25844
|
+
var currentFactor = values.zoom;
|
|
25845
|
+
scaleFactor = currentFactor;
|
|
25846
|
+
|
|
25847
|
+
// Calculate new scroll position to maintain focal point
|
|
25848
|
+
var currentScreenSize = [chart.width(), chart.height()];
|
|
25849
|
+
var newScrollPos;
|
|
25850
|
+
|
|
25851
|
+
if (focalPoint) {
|
|
25852
|
+
// Keep the focal point at the same screen position
|
|
25853
|
+
newScrollPos = [
|
|
25854
|
+
center[0] * scaleFactor - focalPoint[0],
|
|
25855
|
+
center[1] * scaleFactor - focalPoint[1]
|
|
25856
|
+
];
|
|
25857
|
+
} else {
|
|
25858
|
+
// Keep viewport center steady
|
|
25859
|
+
newScrollPos = [
|
|
25860
|
+
center[0] * scaleFactor - currentScreenSize[0]/2,
|
|
25861
|
+
center[1] * scaleFactor - currentScreenSize[1]/2
|
|
25862
|
+
];
|
|
25863
|
+
}
|
|
25864
|
+
|
|
25865
|
+
chart.scrollLeft(newScrollPos[0]);
|
|
25866
|
+
chart.scrollTop(newScrollPos[1]);
|
|
25867
|
+
|
|
25868
|
+
// During animation, only update the scale transform, not the full redraw
|
|
25869
|
+
// This is much more performant with many nodes
|
|
25870
|
+
eventLayer.attr("transform", "scale(" + scaleFactor + ")");
|
|
25871
|
+
outer.attr("width", space_width * scaleFactor).attr("height", space_height * scaleFactor);
|
|
25872
|
+
RED.view.navigator.resize();
|
|
25873
|
+
},
|
|
25874
|
+
onStart: function() {
|
|
25875
|
+
// Show minimap when zoom animation starts
|
|
25876
|
+
RED.events.emit("view:navigate");
|
|
25877
|
+
},
|
|
25878
|
+
onEnd: function() {
|
|
25879
|
+
cancelInProgressAnimation = null;
|
|
25880
|
+
// Ensure scaleFactor is exactly the target to prevent precision issues
|
|
25881
|
+
scaleFactor = targetFactor;
|
|
25882
|
+
// Full redraw at the end to ensure everything is correct
|
|
25883
|
+
redraw();
|
|
25884
|
+
if (RED.settings.get("editor.view.view-store-zoom")) {
|
|
25885
|
+
RED.settings.setLocal('zoom-level', targetFactor.toFixed(1));
|
|
25886
|
+
}
|
|
25887
|
+
},
|
|
25888
|
+
onCancel: function() {
|
|
25889
|
+
cancelInProgressAnimation = null;
|
|
25890
|
+
// Ensure scaleFactor is set to current target on cancel
|
|
25891
|
+
scaleFactor = targetFactor;
|
|
25892
|
+
}
|
|
25893
|
+
});
|
|
25894
|
+
}
|
|
25895
|
+
|
|
25896
|
+
// Momentum scrolling functions
|
|
25897
|
+
function startMomentumScroll() {
|
|
25898
|
+
if (scrollAnimationId) {
|
|
25899
|
+
cancelAnimationFrame(scrollAnimationId);
|
|
25900
|
+
}
|
|
25901
|
+
momentumActive = true;
|
|
25902
|
+
animateMomentumScroll();
|
|
25903
|
+
}
|
|
25904
|
+
|
|
25905
|
+
function animateMomentumScroll() {
|
|
25906
|
+
if (!momentumActive) {
|
|
25907
|
+
return;
|
|
25908
|
+
}
|
|
25909
|
+
|
|
25910
|
+
var scrollX = chart.scrollLeft();
|
|
25911
|
+
var scrollY = chart.scrollTop();
|
|
25912
|
+
var maxScrollX = chart[0].scrollWidth - chart.width();
|
|
25913
|
+
var maxScrollY = chart[0].scrollHeight - chart.height();
|
|
25914
|
+
|
|
25915
|
+
// Apply friction
|
|
25916
|
+
scrollVelocity.x *= FRICTION;
|
|
25917
|
+
scrollVelocity.y *= FRICTION;
|
|
25918
|
+
|
|
25919
|
+
// Check for edges and apply bounce
|
|
25920
|
+
var newScrollX = scrollX + scrollVelocity.x;
|
|
25921
|
+
var newScrollY = scrollY + scrollVelocity.y;
|
|
25922
|
+
|
|
25923
|
+
// Bounce effect at edges
|
|
25924
|
+
if (newScrollX < 0) {
|
|
25925
|
+
newScrollX = 0;
|
|
25926
|
+
scrollVelocity.x = -scrollVelocity.x * BOUNCE_DAMPING;
|
|
25927
|
+
} else if (newScrollX > maxScrollX) {
|
|
25928
|
+
newScrollX = maxScrollX;
|
|
25929
|
+
scrollVelocity.x = -scrollVelocity.x * BOUNCE_DAMPING;
|
|
25930
|
+
}
|
|
25931
|
+
|
|
25932
|
+
if (newScrollY < 0) {
|
|
25933
|
+
newScrollY = 0;
|
|
25934
|
+
scrollVelocity.y = -scrollVelocity.y * BOUNCE_DAMPING;
|
|
25935
|
+
} else if (newScrollY > maxScrollY) {
|
|
25936
|
+
newScrollY = maxScrollY;
|
|
25937
|
+
scrollVelocity.y = -scrollVelocity.y * BOUNCE_DAMPING;
|
|
25938
|
+
}
|
|
25939
|
+
|
|
25940
|
+
// Apply new scroll position
|
|
25941
|
+
chart.scrollLeft(newScrollX);
|
|
25942
|
+
chart.scrollTop(newScrollY);
|
|
25943
|
+
|
|
25944
|
+
// Stop if velocity is too small
|
|
25945
|
+
if (Math.abs(scrollVelocity.x) < MIN_VELOCITY && Math.abs(scrollVelocity.y) < MIN_VELOCITY) {
|
|
25946
|
+
momentumActive = false;
|
|
25947
|
+
scrollVelocity.x = 0;
|
|
25948
|
+
scrollVelocity.y = 0;
|
|
25949
|
+
} else {
|
|
25950
|
+
scrollAnimationId = requestAnimationFrame(animateMomentumScroll);
|
|
25951
|
+
}
|
|
25952
|
+
}
|
|
25953
|
+
|
|
25954
|
+
function handleScroll() {
|
|
25955
|
+
var now = Date.now();
|
|
25956
|
+
var scrollX = chart.scrollLeft();
|
|
25957
|
+
var scrollY = chart.scrollTop();
|
|
25958
|
+
|
|
25959
|
+
if (lastScrollTime) {
|
|
25960
|
+
var dt = now - lastScrollTime;
|
|
25961
|
+
if (dt > 0 && dt < 100) { // Only calculate velocity for recent scrolls
|
|
25962
|
+
scrollVelocity.x = (scrollX - lastScrollPos.x) / dt * 16; // Normalize to 60fps
|
|
25963
|
+
scrollVelocity.y = (scrollY - lastScrollPos.y) / dt * 16;
|
|
25964
|
+
}
|
|
25965
|
+
}
|
|
25966
|
+
|
|
25967
|
+
lastScrollTime = now;
|
|
25968
|
+
lastScrollPos.x = scrollX;
|
|
25969
|
+
lastScrollPos.y = scrollY;
|
|
25970
|
+
|
|
25971
|
+
// Cancel any ongoing momentum animation
|
|
25972
|
+
if (scrollAnimationId) {
|
|
25973
|
+
cancelAnimationFrame(scrollAnimationId);
|
|
25974
|
+
scrollAnimationId = null;
|
|
25975
|
+
}
|
|
25976
|
+
}
|
|
25977
|
+
|
|
25151
25978
|
function selectNone() {
|
|
25152
25979
|
if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
|
|
25153
25980
|
return;
|
|
@@ -25852,6 +26679,10 @@ RED.view = (function() {
|
|
|
25852
26679
|
|
|
25853
26680
|
function portMouseDown(d,portType,portIndex, evt) {
|
|
25854
26681
|
if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); }
|
|
26682
|
+
if (spacebarPressed) {
|
|
26683
|
+
return
|
|
26684
|
+
}
|
|
26685
|
+
clearSuggestedFlow();
|
|
25855
26686
|
RED.contextMenu.hide();
|
|
25856
26687
|
evt = evt || d3.event;
|
|
25857
26688
|
if (evt === 1) {
|
|
@@ -26275,11 +27106,14 @@ RED.view = (function() {
|
|
|
26275
27106
|
return tooltip;
|
|
26276
27107
|
}
|
|
26277
27108
|
|
|
26278
|
-
function portMouseOver(port,d,portType,portIndex) {
|
|
27109
|
+
function portMouseOver(port,d,portType,portIndex, event) {
|
|
26279
27110
|
if (mouse_mode === RED.state.SELECTING_NODE) {
|
|
26280
|
-
d3.event.stopPropagation();
|
|
27111
|
+
(d3.event || event).stopPropagation();
|
|
26281
27112
|
return;
|
|
26282
27113
|
}
|
|
27114
|
+
if (spacebarPressed) {
|
|
27115
|
+
return
|
|
27116
|
+
}
|
|
26283
27117
|
clearTimeout(portLabelHoverTimeout);
|
|
26284
27118
|
var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active
|
|
26285
27119
|
(
|
|
@@ -26316,9 +27150,9 @@ RED.view = (function() {
|
|
|
26316
27150
|
}
|
|
26317
27151
|
port.classed("red-ui-flow-port-hovered",active);
|
|
26318
27152
|
}
|
|
26319
|
-
function portMouseOut(port,d,portType,portIndex) {
|
|
27153
|
+
function portMouseOut(port,d,portType,portIndex, event) {
|
|
26320
27154
|
if (mouse_mode === RED.state.SELECTING_NODE) {
|
|
26321
|
-
d3.event.stopPropagation();
|
|
27155
|
+
(d3.event || event).stopPropagation();
|
|
26322
27156
|
return;
|
|
26323
27157
|
}
|
|
26324
27158
|
clearTimeout(portLabelHoverTimeout);
|
|
@@ -26436,6 +27270,10 @@ RED.view = (function() {
|
|
|
26436
27270
|
}
|
|
26437
27271
|
function nodeMouseDown(d) {
|
|
26438
27272
|
if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); }
|
|
27273
|
+
if (spacebarPressed) {
|
|
27274
|
+
return
|
|
27275
|
+
}
|
|
27276
|
+
clearSuggestedFlow()
|
|
26439
27277
|
focusView();
|
|
26440
27278
|
RED.contextMenu.hide();
|
|
26441
27279
|
if (d3.event.button === 1) {
|
|
@@ -26613,6 +27451,9 @@ RED.view = (function() {
|
|
|
26613
27451
|
|
|
26614
27452
|
function nodeMouseOver(d) {
|
|
26615
27453
|
if (RED.view.DEBUG) { console.warn("nodeMouseOver", mouse_mode,d); }
|
|
27454
|
+
if (spacebarPressed) {
|
|
27455
|
+
return
|
|
27456
|
+
}
|
|
26616
27457
|
if (mouse_mode === 0 || mouse_mode === RED.state.SELECTING_NODE) {
|
|
26617
27458
|
if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions && selectNodesOptions.filter) {
|
|
26618
27459
|
if (selectNodesOptions.filter(d)) {
|
|
@@ -26786,6 +27627,9 @@ RED.view = (function() {
|
|
|
26786
27627
|
if (RED.view.DEBUG) {
|
|
26787
27628
|
console.warn("groupMouseDown", { mouse_mode, point: mouse, event: d3.event });
|
|
26788
27629
|
}
|
|
27630
|
+
if (spacebarPressed) {
|
|
27631
|
+
return
|
|
27632
|
+
}
|
|
26789
27633
|
RED.contextMenu.hide();
|
|
26790
27634
|
focusView();
|
|
26791
27635
|
if (d3.event.button === 1) {
|
|
@@ -27061,130 +27905,220 @@ RED.view = (function() {
|
|
|
27061
27905
|
}
|
|
27062
27906
|
}
|
|
27063
27907
|
|
|
27908
|
+
function buildSubflowPort (d) {
|
|
27909
|
+
const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
|
|
27910
|
+
// PORT_TYPE is the 'opposite' of NODE_TYPE
|
|
27911
|
+
const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
|
|
27912
|
+
var node = d3.select(this);
|
|
27913
|
+
var nodeContents = document.createDocumentFragment();
|
|
27914
|
+
|
|
27915
|
+
d.h = 40;
|
|
27916
|
+
d.resize = true;
|
|
27917
|
+
d.dirty = true;
|
|
27918
|
+
|
|
27919
|
+
var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
27920
|
+
mainRect.__data__ = d;
|
|
27921
|
+
mainRect.setAttribute("class", "red-ui-flow-subflow-port");
|
|
27922
|
+
mainRect.setAttribute("rx", 8);
|
|
27923
|
+
mainRect.setAttribute("ry", 8);
|
|
27924
|
+
mainRect.setAttribute("width", 40);
|
|
27925
|
+
mainRect.setAttribute("height", 40);
|
|
27926
|
+
node[0][0].__mainRect__ = mainRect;
|
|
27927
|
+
d3.select(mainRect)
|
|
27928
|
+
.on("mouseup",nodeMouseUp)
|
|
27929
|
+
.on("mousedown",nodeMouseDown)
|
|
27930
|
+
.on("touchstart",nodeTouchStart)
|
|
27931
|
+
.on("touchend",nodeTouchEnd)
|
|
27932
|
+
nodeContents.appendChild(mainRect);
|
|
27933
|
+
|
|
27934
|
+
const port_label_group = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27935
|
+
port_label_group.setAttribute("x",0);
|
|
27936
|
+
port_label_group.setAttribute("y",0);
|
|
27937
|
+
node[0][0].__portLabelGroup__ = port_label_group;
|
|
27938
|
+
|
|
27939
|
+
const port_label = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
27940
|
+
port_label.setAttribute("class","red-ui-flow-port-label");
|
|
27941
|
+
port_label.style["font-size"] = "10px";
|
|
27942
|
+
port_label.textContent = NODE_TYPE === PORT_TYPE_INPUT? "input" : "output";
|
|
27943
|
+
port_label_group.appendChild(port_label);
|
|
27944
|
+
node[0][0].__portLabel__ = port_label;
|
|
27945
|
+
|
|
27946
|
+
if (NODE_TYPE === PORT_TYPE_OUTPUT) {
|
|
27947
|
+
const port_number = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
27948
|
+
port_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
|
|
27949
|
+
port_number.setAttribute("x",0);
|
|
27950
|
+
port_number.setAttribute("y",0);
|
|
27951
|
+
port_number.textContent = d.i+1;
|
|
27952
|
+
port_label_group.appendChild(port_number);
|
|
27953
|
+
node[0][0].__portNumber__ = port_number;
|
|
27954
|
+
}
|
|
27955
|
+
|
|
27956
|
+
const port_border = document.createElementNS("http://www.w3.org/2000/svg","path");
|
|
27957
|
+
port_border.setAttribute("d","M 40 1 l 0 38")
|
|
27958
|
+
port_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
|
|
27959
|
+
port_label_group.appendChild(port_border);
|
|
27960
|
+
node[0][0].__portBorder__ = port_border;
|
|
27961
|
+
|
|
27962
|
+
nodeContents.appendChild(port_label_group);
|
|
27963
|
+
|
|
27964
|
+
var text = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27965
|
+
text.setAttribute("class","red-ui-flow-port-label");
|
|
27966
|
+
text.setAttribute("transform","translate(38,0)");
|
|
27967
|
+
text.setAttribute('style', 'fill : #888'); // hard coded here!
|
|
27968
|
+
node[0][0].__textGroup__ = text;
|
|
27969
|
+
nodeContents.append(text);
|
|
27970
|
+
|
|
27971
|
+
var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27972
|
+
portEl.setAttribute('transform','translate(-5,15)')
|
|
27973
|
+
|
|
27974
|
+
var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
27975
|
+
port.setAttribute("class","red-ui-flow-port");
|
|
27976
|
+
port.setAttribute("rx",3);
|
|
27977
|
+
port.setAttribute("ry",3);
|
|
27978
|
+
port.setAttribute("width",10);
|
|
27979
|
+
port.setAttribute("height",10);
|
|
27980
|
+
portEl.appendChild(port);
|
|
27981
|
+
port.__data__ = d;
|
|
27982
|
+
|
|
27983
|
+
d3.select(port)
|
|
27984
|
+
.on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE,0);} )
|
|
27985
|
+
.on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE,0);d3.event.preventDefault();} )
|
|
27986
|
+
.on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE,0);})
|
|
27987
|
+
.on("touchend",function(d,i){portMouseUp(d,PORT_TYPE,0);d3.event.preventDefault();} )
|
|
27988
|
+
.on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE,0);})
|
|
27989
|
+
.on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE,0);});
|
|
27990
|
+
|
|
27991
|
+
node[0][0].__port__ = portEl
|
|
27992
|
+
nodeContents.appendChild(portEl);
|
|
27993
|
+
node[0][0].appendChild(nodeContents);
|
|
27994
|
+
}
|
|
27995
|
+
function updateSubflowPort (d) {
|
|
27996
|
+
if (d.dirty) {
|
|
27997
|
+
const port_height = 40;
|
|
27998
|
+
const NODE_TYPE = d.direction === "in" ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT;
|
|
27999
|
+
// PORT_TYPE is the 'opposite' of NODE_TYPE
|
|
28000
|
+
const PORT_TYPE = NODE_TYPE === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT;
|
|
28001
|
+
|
|
28002
|
+
var label = getPortLabel(activeSubflow, NODE_TYPE, d.i) || "";
|
|
28003
|
+
var hideLabel = (label.length < 1)
|
|
28004
|
+
var labelParts;
|
|
28005
|
+
if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
|
|
28006
|
+
labelParts = getLabelParts(label, "red-ui-flow-node-label");
|
|
28007
|
+
if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
|
|
28008
|
+
d.resize = true;
|
|
28009
|
+
}
|
|
28010
|
+
this.__label__ = label;
|
|
28011
|
+
this.__labelLineCount__ = labelParts.lines.length;
|
|
28012
|
+
|
|
28013
|
+
if (hideLabel) {
|
|
28014
|
+
d.h = Math.max(port_height,(d.outputs || 0) * 15);
|
|
28015
|
+
} else {
|
|
28016
|
+
d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
|
|
28017
|
+
}
|
|
28018
|
+
this.__hideLabel__ = hideLabel;
|
|
28019
|
+
}
|
|
28020
|
+
|
|
28021
|
+
if (d.resize) {
|
|
28022
|
+
var ow = d.w;
|
|
28023
|
+
if (hideLabel) {
|
|
28024
|
+
d.w = port_height;
|
|
28025
|
+
} else {
|
|
28026
|
+
d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
|
|
28027
|
+
}
|
|
28028
|
+
if (ow !== undefined) {
|
|
28029
|
+
d.x += (d.w-ow)/2;
|
|
28030
|
+
}
|
|
28031
|
+
d.resize = false;
|
|
28032
|
+
}
|
|
28033
|
+
|
|
28034
|
+
this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
|
|
28035
|
+
// This might be the first redraw after a node has been click-dragged to start a move.
|
|
28036
|
+
// So its selected state might have changed since the last redraw.
|
|
28037
|
+
this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
|
|
28038
|
+
if (mouse_mode != RED.state.MOVING_ACTIVE) {
|
|
28039
|
+
this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
|
|
28040
|
+
this.__mainRect__.setAttribute("width", d.w)
|
|
28041
|
+
this.__mainRect__.setAttribute("height", d.h)
|
|
28042
|
+
this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
|
|
28043
|
+
|
|
28044
|
+
if (labelParts) {
|
|
28045
|
+
// The label has changed
|
|
28046
|
+
var sa = labelParts.lines;
|
|
28047
|
+
var sn = labelParts.lines.length;
|
|
28048
|
+
var textLines = this.__textGroup__.childNodes;
|
|
28049
|
+
while(textLines.length > sn) {
|
|
28050
|
+
textLines[textLines.length-1].remove();
|
|
28051
|
+
}
|
|
28052
|
+
for (var i=0; i<sn; i++) {
|
|
28053
|
+
if (i===textLines.length) {
|
|
28054
|
+
var line = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
28055
|
+
line.setAttribute("class","red-ui-flow-node-label-text");
|
|
28056
|
+
line.setAttribute("x",0);
|
|
28057
|
+
line.setAttribute("y",i*24);
|
|
28058
|
+
this.__textGroup__.appendChild(line);
|
|
28059
|
+
}
|
|
28060
|
+
textLines[i].textContent = sa[i];
|
|
28061
|
+
}
|
|
28062
|
+
}
|
|
28063
|
+
|
|
28064
|
+
var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
|
|
28065
|
+
this.__textGroup__.setAttribute("class", textClass);
|
|
28066
|
+
var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
|
|
28067
|
+
|
|
28068
|
+
// this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
|
|
28069
|
+
this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
|
|
28070
|
+
|
|
28071
|
+
this.__portBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
|
|
28072
|
+
const portX = PORT_TYPE === PORT_TYPE_OUTPUT ? d.w - 5 : -5
|
|
28073
|
+
this.__port__.setAttribute("transform","translate("+portX+","+((d.h/2)-5)+")");
|
|
28074
|
+
if (NODE_TYPE === PORT_TYPE_OUTPUT) {
|
|
28075
|
+
this.__portLabel__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
|
|
28076
|
+
this.__portNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
|
|
28077
|
+
this.__portNumber__.textContent = d.i+1;
|
|
28078
|
+
} else {
|
|
28079
|
+
this.__portLabel__.setAttribute("transform","translate(20,"+(d.h/2)+")");
|
|
28080
|
+
}
|
|
28081
|
+
}
|
|
28082
|
+
d.dirty = false;
|
|
28083
|
+
return true
|
|
28084
|
+
}
|
|
28085
|
+
return false
|
|
28086
|
+
}
|
|
28087
|
+
|
|
27064
28088
|
function _redraw() {
|
|
27065
28089
|
eventLayer.attr("transform","scale("+scaleFactor+")");
|
|
27066
28090
|
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
|
|
27067
28091
|
|
|
27068
|
-
//
|
|
27069
|
-
|
|
28092
|
+
// Update scroll spacer to match scaled canvas size
|
|
28093
|
+
// This ensures scrollable area = canvas area
|
|
28094
|
+
// Browser calculates maxScroll = scrollWidth - viewport, which correctly
|
|
28095
|
+
// allows scrolling to see the far edges of canvas without going beyond
|
|
28096
|
+
$('#red-ui-workspace-scroll-spacer').css({
|
|
28097
|
+
width: (space_width * scaleFactor) + 'px',
|
|
28098
|
+
height: (space_height * scaleFactor) + 'px'
|
|
28099
|
+
});
|
|
27070
28100
|
|
|
28101
|
+
// Don't bother redrawing nodes if we're drawing links
|
|
28102
|
+
if (forceFullRedraw || showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
|
|
28103
|
+
forceFullRedraw = false
|
|
27071
28104
|
var dirtyNodes = {};
|
|
27072
28105
|
|
|
27073
28106
|
if (activeSubflow) {
|
|
27074
28107
|
var subflowOutputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-output").data(activeSubflow.out,function(d,i){ return d.id;});
|
|
27075
28108
|
subflowOutputs.exit().remove();
|
|
27076
28109
|
var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-output")
|
|
27077
|
-
outGroup.each(
|
|
27078
|
-
var node = d3.select(this);
|
|
27079
|
-
var nodeContents = document.createDocumentFragment();
|
|
27080
|
-
|
|
27081
|
-
d.h = 40;
|
|
27082
|
-
d.resize = true;
|
|
27083
|
-
d.dirty = true;
|
|
27084
|
-
|
|
27085
|
-
var mainRect = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
27086
|
-
mainRect.__data__ = d;
|
|
27087
|
-
mainRect.setAttribute("class", "red-ui-flow-subflow-port");
|
|
27088
|
-
mainRect.setAttribute("rx", 8);
|
|
27089
|
-
mainRect.setAttribute("ry", 8);
|
|
27090
|
-
mainRect.setAttribute("width", 40);
|
|
27091
|
-
mainRect.setAttribute("height", 40);
|
|
27092
|
-
node[0][0].__mainRect__ = mainRect;
|
|
27093
|
-
d3.select(mainRect)
|
|
27094
|
-
.on("mouseup",nodeMouseUp)
|
|
27095
|
-
.on("mousedown",nodeMouseDown)
|
|
27096
|
-
.on("touchstart",nodeTouchStart)
|
|
27097
|
-
.on("touchend",nodeTouchEnd)
|
|
27098
|
-
nodeContents.appendChild(mainRect);
|
|
27099
|
-
|
|
27100
|
-
var output_groupEl = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27101
|
-
output_groupEl.setAttribute("x",0);
|
|
27102
|
-
output_groupEl.setAttribute("y",0);
|
|
27103
|
-
node[0][0].__outputLabelGroup__ = output_groupEl;
|
|
27104
|
-
|
|
27105
|
-
var output_output = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
27106
|
-
output_output.setAttribute("class","red-ui-flow-port-label");
|
|
27107
|
-
output_output.style["font-size"] = "10px";
|
|
27108
|
-
output_output.textContent = "output";
|
|
27109
|
-
output_groupEl.appendChild(output_output);
|
|
27110
|
-
node[0][0].__outputOutput__ = output_output;
|
|
27111
|
-
|
|
27112
|
-
var output_number = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
27113
|
-
output_number.setAttribute("class","red-ui-flow-port-label red-ui-flow-port-index");
|
|
27114
|
-
output_number.setAttribute("x",0);
|
|
27115
|
-
output_number.setAttribute("y",0);
|
|
27116
|
-
output_number.textContent = d.i+1;
|
|
27117
|
-
output_groupEl.appendChild(output_number);
|
|
27118
|
-
node[0][0].__outputNumber__ = output_number;
|
|
27119
|
-
|
|
27120
|
-
var output_border = document.createElementNS("http://www.w3.org/2000/svg","path");
|
|
27121
|
-
output_border.setAttribute("d","M 40 1 l 0 38")
|
|
27122
|
-
output_border.setAttribute("class", "red-ui-flow-node-icon-shade-border")
|
|
27123
|
-
output_groupEl.appendChild(output_border);
|
|
27124
|
-
node[0][0].__outputBorder__ = output_border;
|
|
27125
|
-
|
|
27126
|
-
nodeContents.appendChild(output_groupEl);
|
|
27127
|
-
|
|
27128
|
-
var text = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27129
|
-
text.setAttribute("class","red-ui-flow-port-label");
|
|
27130
|
-
text.setAttribute("transform","translate(38,0)");
|
|
27131
|
-
text.setAttribute('style', 'fill : #888'); // hard coded here!
|
|
27132
|
-
node[0][0].__textGroup__ = text;
|
|
27133
|
-
nodeContents.append(text);
|
|
27134
|
-
|
|
27135
|
-
var portEl = document.createElementNS("http://www.w3.org/2000/svg","g");
|
|
27136
|
-
portEl.setAttribute('transform','translate(-5,15)')
|
|
27137
|
-
|
|
27138
|
-
var port = document.createElementNS("http://www.w3.org/2000/svg","rect");
|
|
27139
|
-
port.setAttribute("class","red-ui-flow-port");
|
|
27140
|
-
port.setAttribute("rx",3);
|
|
27141
|
-
port.setAttribute("ry",3);
|
|
27142
|
-
port.setAttribute("width",10);
|
|
27143
|
-
port.setAttribute("height",10);
|
|
27144
|
-
portEl.appendChild(port);
|
|
27145
|
-
port.__data__ = d;
|
|
27146
|
-
|
|
27147
|
-
d3.select(port)
|
|
27148
|
-
.on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);} )
|
|
27149
|
-
.on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
|
|
27150
|
-
.on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);})
|
|
27151
|
-
.on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_INPUT,0);d3.event.preventDefault();} )
|
|
27152
|
-
.on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_INPUT,0);})
|
|
27153
|
-
.on("mouseout",function(d){portMouseOut(d3.select(this),d,PORT_TYPE_INPUT,0);});
|
|
27154
|
-
|
|
27155
|
-
node[0][0].__port__ = portEl
|
|
27156
|
-
nodeContents.appendChild(portEl);
|
|
27157
|
-
node[0][0].appendChild(nodeContents);
|
|
27158
|
-
});
|
|
28110
|
+
outGroup.each(buildSubflowPort);
|
|
27159
28111
|
|
|
27160
28112
|
var subflowInputs = nodeLayer.selectAll(".red-ui-flow-subflow-port-input").data(activeSubflow.in,function(d,i){ return d.id;});
|
|
27161
28113
|
subflowInputs.exit().remove();
|
|
27162
28114
|
var inGroup = subflowInputs.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-input").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
|
|
27163
|
-
inGroup.each(
|
|
27164
|
-
d.w=40;
|
|
27165
|
-
d.h=40;
|
|
27166
|
-
});
|
|
27167
|
-
inGroup.append("rect").attr("class","red-ui-flow-subflow-port").attr("rx",8).attr("ry",8).attr("width",40).attr("height",40)
|
|
27168
|
-
// TODO: This is exactly the same set of handlers used for regular nodes - DRY
|
|
27169
|
-
.on("mouseup",nodeMouseUp)
|
|
27170
|
-
.on("mousedown",nodeMouseDown)
|
|
27171
|
-
.on("touchstart",nodeTouchStart)
|
|
27172
|
-
.on("touchend", nodeTouchEnd);
|
|
27173
|
-
|
|
27174
|
-
inGroup.append("g").attr('transform','translate(35,15)').append("rect").attr("class","red-ui-flow-port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
|
|
27175
|
-
.on("mousedown", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);} )
|
|
27176
|
-
.on("touchstart", function(d,i){portMouseDown(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
|
|
27177
|
-
.on("mouseup", function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);})
|
|
27178
|
-
.on("touchend",function(d,i){portMouseUp(d,PORT_TYPE_OUTPUT,i);d3.event.preventDefault();} )
|
|
27179
|
-
.on("mouseover",function(d){portMouseOver(d3.select(this),d,PORT_TYPE_OUTPUT,0);})
|
|
27180
|
-
.on("mouseout",function(d) {portMouseOut(d3.select(this),d,PORT_TYPE_OUTPUT,0);});
|
|
27181
|
-
|
|
27182
|
-
inGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",18).attr("y",20).style("font-size","10px").text("input");
|
|
28115
|
+
inGroup.each(buildSubflowPort);
|
|
27183
28116
|
|
|
27184
28117
|
var subflowStatus = nodeLayer.selectAll(".red-ui-flow-subflow-port-status").data(activeSubflow.status?[activeSubflow.status]:[],function(d,i){ return d.id;});
|
|
27185
28118
|
subflowStatus.exit().remove();
|
|
27186
28119
|
|
|
27187
28120
|
var statusGroup = subflowStatus.enter().insert("svg:g").attr("class","red-ui-flow-node red-ui-flow-subflow-port-status").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
|
|
28121
|
+
// TODO: use buildSubflowPort/updateSubflowPort for status port
|
|
27188
28122
|
statusGroup.each(function(d,i) {
|
|
27189
28123
|
d.w=40;
|
|
27190
28124
|
d.h=40;
|
|
@@ -27206,104 +28140,16 @@ RED.view = (function() {
|
|
|
27206
28140
|
|
|
27207
28141
|
statusGroup.append("svg:text").attr("class","red-ui-flow-port-label").attr("x",22).attr("y",20).style("font-size","10px").text("status");
|
|
27208
28142
|
|
|
27209
|
-
subflowOutputs.each(function(d,i) {
|
|
27210
|
-
if (d
|
|
27211
|
-
|
|
27212
|
-
var port_height = 40;
|
|
27213
|
-
|
|
27214
|
-
var self = this;
|
|
27215
|
-
var thisNode = d3.select(this);
|
|
27216
|
-
|
|
28143
|
+
subflowOutputs.each(function (d,i) {
|
|
28144
|
+
if (updateSubflowPort.call(this, d)) {
|
|
27217
28145
|
dirtyNodes[d.id] = d;
|
|
27218
|
-
|
|
27219
|
-
var label = getPortLabel(activeSubflow, PORT_TYPE_OUTPUT, d.i) || "";
|
|
27220
|
-
var hideLabel = (label.length < 1)
|
|
27221
|
-
|
|
27222
|
-
var labelParts;
|
|
27223
|
-
if (d.resize || this.__hideLabel__ !== hideLabel || this.__label__ !== label) {
|
|
27224
|
-
labelParts = getLabelParts(label, "red-ui-flow-node-label");
|
|
27225
|
-
if (labelParts.lines.length !== this.__labelLineCount__ || this.__label__ !== label) {
|
|
27226
|
-
d.resize = true;
|
|
27227
|
-
}
|
|
27228
|
-
this.__label__ = label;
|
|
27229
|
-
this.__labelLineCount__ = labelParts.lines.length;
|
|
27230
|
-
|
|
27231
|
-
if (hideLabel) {
|
|
27232
|
-
d.h = Math.max(port_height,(d.outputs || 0) * 15);
|
|
27233
|
-
} else {
|
|
27234
|
-
d.h = Math.max(6+24*labelParts.lines.length,(d.outputs || 0) * 15, port_height);
|
|
27235
|
-
}
|
|
27236
|
-
this.__hideLabel__ = hideLabel;
|
|
27237
|
-
}
|
|
27238
|
-
|
|
27239
|
-
if (d.resize) {
|
|
27240
|
-
var ow = d.w;
|
|
27241
|
-
if (hideLabel) {
|
|
27242
|
-
d.w = port_height;
|
|
27243
|
-
} else {
|
|
27244
|
-
d.w = Math.max(port_height,20*(Math.ceil((labelParts.width+50+7)/20)) );
|
|
27245
|
-
}
|
|
27246
|
-
if (ow !== undefined) {
|
|
27247
|
-
d.x += (d.w-ow)/2;
|
|
27248
|
-
}
|
|
27249
|
-
d.resize = false;
|
|
27250
|
-
}
|
|
27251
|
-
|
|
27252
|
-
this.setAttribute("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")");
|
|
27253
|
-
// This might be the first redraw after a node has been click-dragged to start a move.
|
|
27254
|
-
// So its selected state might have changed since the last redraw.
|
|
27255
|
-
this.classList.toggle("red-ui-flow-node-selected", !!d.selected )
|
|
27256
|
-
if (mouse_mode != RED.state.MOVING_ACTIVE) {
|
|
27257
|
-
this.classList.toggle("red-ui-flow-node-disabled", d.d === true);
|
|
27258
|
-
this.__mainRect__.setAttribute("width", d.w)
|
|
27259
|
-
this.__mainRect__.setAttribute("height", d.h)
|
|
27260
|
-
this.__mainRect__.classList.toggle("red-ui-flow-node-highlighted",!!d.highlighted );
|
|
27261
|
-
|
|
27262
|
-
if (labelParts) {
|
|
27263
|
-
// The label has changed
|
|
27264
|
-
var sa = labelParts.lines;
|
|
27265
|
-
var sn = labelParts.lines.length;
|
|
27266
|
-
var textLines = this.__textGroup__.childNodes;
|
|
27267
|
-
while(textLines.length > sn) {
|
|
27268
|
-
textLines[textLines.length-1].remove();
|
|
27269
|
-
}
|
|
27270
|
-
for (var i=0; i<sn; i++) {
|
|
27271
|
-
if (i===textLines.length) {
|
|
27272
|
-
var line = document.createElementNS("http://www.w3.org/2000/svg","text");
|
|
27273
|
-
line.setAttribute("class","red-ui-flow-node-label-text");
|
|
27274
|
-
line.setAttribute("x",0);
|
|
27275
|
-
line.setAttribute("y",i*24);
|
|
27276
|
-
this.__textGroup__.appendChild(line);
|
|
27277
|
-
}
|
|
27278
|
-
textLines[i].textContent = sa[i];
|
|
27279
|
-
}
|
|
27280
|
-
}
|
|
27281
|
-
|
|
27282
|
-
var textClass = "red-ui-flow-node-label"+(hideLabel?" hide":"");
|
|
27283
|
-
this.__textGroup__.setAttribute("class", textClass);
|
|
27284
|
-
var yp = d.h / 2 - (this.__labelLineCount__ / 2) * 24 + 13;
|
|
27285
|
-
|
|
27286
|
-
// this.__textGroup__.classList.remove("red-ui-flow-node-label-right");
|
|
27287
|
-
this.__textGroup__.setAttribute("transform", "translate(48,"+yp+")");
|
|
27288
|
-
|
|
27289
|
-
this.__outputBorder__.setAttribute("d","M 40 1 l 0 "+(hideLabel?0:(d.h - 2)));
|
|
27290
|
-
this.__port__.setAttribute("transform","translate(-5,"+((d.h/2)-5)+")");
|
|
27291
|
-
this.__outputOutput__.setAttribute("transform","translate(20,"+((d.h/2)-8)+")");
|
|
27292
|
-
this.__outputNumber__.setAttribute("transform","translate(20,"+((d.h/2)+7)+")");
|
|
27293
|
-
this.__outputNumber__.textContent = d.i+1;
|
|
27294
|
-
}
|
|
27295
|
-
d.dirty = false;
|
|
27296
28146
|
}
|
|
27297
|
-
})
|
|
27298
|
-
subflowInputs.each(function(d,i) {
|
|
27299
|
-
if (d
|
|
27300
|
-
var input = d3.select(this);
|
|
27301
|
-
input.classed("red-ui-flow-node-selected",function(d) { return d.selected; })
|
|
27302
|
-
input.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; });
|
|
28147
|
+
})
|
|
28148
|
+
subflowInputs.each(function (d,i) {
|
|
28149
|
+
if (updateSubflowPort.call(this, d)) {
|
|
27303
28150
|
dirtyNodes[d.id] = d;
|
|
27304
|
-
d.dirty = false;
|
|
27305
28151
|
}
|
|
27306
|
-
})
|
|
28152
|
+
})
|
|
27307
28153
|
subflowStatus.each(function(d,i) {
|
|
27308
28154
|
if (d.dirty) {
|
|
27309
28155
|
var output = d3.select(this);
|
|
@@ -29357,7 +30203,6 @@ RED.view = (function() {
|
|
|
29357
30203
|
refreshSuggestedFlow();
|
|
29358
30204
|
} else { // Anything else; clear the suggestion
|
|
29359
30205
|
clearSuggestedFlow();
|
|
29360
|
-
RED.view.redraw(true);
|
|
29361
30206
|
// manually push the event to the keyboard handler
|
|
29362
30207
|
RED.keyboard.handle(evt)
|
|
29363
30208
|
}
|
|
@@ -29366,7 +30211,6 @@ RED.view = (function() {
|
|
|
29366
30211
|
if (suggestion.clickToApply) {
|
|
29367
30212
|
$(window).on('mousedown.suggestedFlow', function (evnt) {
|
|
29368
30213
|
clearSuggestedFlow();
|
|
29369
|
-
RED.view.redraw(true);
|
|
29370
30214
|
})
|
|
29371
30215
|
}
|
|
29372
30216
|
}
|
|
@@ -29387,12 +30231,16 @@ RED.view = (function() {
|
|
|
29387
30231
|
}
|
|
29388
30232
|
|
|
29389
30233
|
function clearSuggestedFlow () {
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29395
|
-
|
|
30234
|
+
if (currentSuggestion) {
|
|
30235
|
+
$(window).off('mousedown.suggestedFlow');
|
|
30236
|
+
$(window).off('keydown.suggestedFlow')
|
|
30237
|
+
RED.keyboard.enable()
|
|
30238
|
+
currentSuggestion = null
|
|
30239
|
+
suggestedNodes = []
|
|
30240
|
+
suggestedLinks = []
|
|
30241
|
+
forceFullRedraw = true
|
|
30242
|
+
RED.view.redraw(true);
|
|
30243
|
+
}
|
|
29396
30244
|
}
|
|
29397
30245
|
|
|
29398
30246
|
function applySuggestedFlow () {
|
|
@@ -29610,7 +30458,7 @@ RED.view = (function() {
|
|
|
29610
30458
|
selectNodes: function(options) {
|
|
29611
30459
|
$("#red-ui-workspace-tabs-shade").show();
|
|
29612
30460
|
$("#red-ui-palette-shade").show();
|
|
29613
|
-
$("
|
|
30461
|
+
$(".red-ui-sidebar-shade").show();
|
|
29614
30462
|
$("#red-ui-header-shade").show();
|
|
29615
30463
|
$("#red-ui-workspace").addClass("red-ui-workspace-select-mode");
|
|
29616
30464
|
|
|
@@ -29632,7 +30480,7 @@ RED.view = (function() {
|
|
|
29632
30480
|
clearSelection();
|
|
29633
30481
|
$("#red-ui-workspace-tabs-shade").hide();
|
|
29634
30482
|
$("#red-ui-palette-shade").hide();
|
|
29635
|
-
$("
|
|
30483
|
+
$(".red-ui-sidebar-shade").hide();
|
|
29636
30484
|
$("#red-ui-header-shade").hide();
|
|
29637
30485
|
$("#red-ui-workspace").removeClass("red-ui-workspace-select-mode");
|
|
29638
30486
|
resetMouseVars();
|
|
@@ -29701,7 +30549,283 @@ RED.view = (function() {
|
|
|
29701
30549
|
applySuggestedFlow
|
|
29702
30550
|
};
|
|
29703
30551
|
})();
|
|
29704
|
-
|
|
30552
|
+
;/**
|
|
30553
|
+
* Zoom configuration constants
|
|
30554
|
+
*/
|
|
30555
|
+
RED.view.zoomConstants = {
|
|
30556
|
+
// Zoom limits
|
|
30557
|
+
MIN_ZOOM: 0.05, // Default minimum, will be dynamically calculated to fit canvas
|
|
30558
|
+
MAX_ZOOM: 2.0,
|
|
30559
|
+
|
|
30560
|
+
// Zoom step for keyboard/button controls
|
|
30561
|
+
ZOOM_STEP: 0.2,
|
|
30562
|
+
|
|
30563
|
+
// Animation settings
|
|
30564
|
+
DEFAULT_ZOOM_DURATION: 125, // ms, faster animation
|
|
30565
|
+
|
|
30566
|
+
// Gesture thresholds
|
|
30567
|
+
PINCH_THRESHOLD: 10, // minimum pixel movement to trigger zoom
|
|
30568
|
+
|
|
30569
|
+
// Momentum and friction for smooth scrolling
|
|
30570
|
+
FRICTION: 0.92,
|
|
30571
|
+
BOUNCE_DAMPING: 0.6
|
|
30572
|
+
};;/**
|
|
30573
|
+
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
30574
|
+
*
|
|
30575
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
30576
|
+
* you may not use this file except in compliance with the License.
|
|
30577
|
+
* You may obtain a copy of the License at
|
|
30578
|
+
*
|
|
30579
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
30580
|
+
*
|
|
30581
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
30582
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
30583
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
30584
|
+
* See the License for the specific language governing permissions and
|
|
30585
|
+
* limitations under the License.
|
|
30586
|
+
**/
|
|
30587
|
+
|
|
30588
|
+
RED.view.zoomAnimator = (function() {
|
|
30589
|
+
|
|
30590
|
+
/**
|
|
30591
|
+
* Easing function for smooth deceleration
|
|
30592
|
+
* Creates natural-feeling animation curves
|
|
30593
|
+
* @param {number} t - Progress from 0 to 1
|
|
30594
|
+
* @returns {number} - Eased value from 0 to 1
|
|
30595
|
+
*/
|
|
30596
|
+
function easeOut(t) {
|
|
30597
|
+
// Cubic ease-out for smooth deceleration
|
|
30598
|
+
return 1 - Math.pow(1 - t, 3);
|
|
30599
|
+
}
|
|
30600
|
+
|
|
30601
|
+
/**
|
|
30602
|
+
* Animate values using requestAnimationFrame with easing
|
|
30603
|
+
* Based on Excalidraw's implementation for smooth zoom transitions
|
|
30604
|
+
*
|
|
30605
|
+
* @param {Object} options - Animation options
|
|
30606
|
+
* @param {Object} options.fromValues - Starting values object
|
|
30607
|
+
* @param {Object} options.toValues - Target values object
|
|
30608
|
+
* @param {Function} options.onStep - Callback for each animation frame
|
|
30609
|
+
* @param {number} [options.duration=250] - Animation duration in ms
|
|
30610
|
+
* @param {Function} [options.interpolateValue] - Custom interpolation function
|
|
30611
|
+
* @param {Function} [options.onStart] - Animation start callback
|
|
30612
|
+
* @param {Function} [options.onEnd] - Animation end callback
|
|
30613
|
+
* @param {Function} [options.onCancel] - Animation cancel callback
|
|
30614
|
+
* @returns {Function} - Cancel function to stop animation
|
|
30615
|
+
*/
|
|
30616
|
+
function easeToValuesRAF(options) {
|
|
30617
|
+
const {
|
|
30618
|
+
fromValues,
|
|
30619
|
+
toValues,
|
|
30620
|
+
onStep,
|
|
30621
|
+
duration = 250,
|
|
30622
|
+
interpolateValue,
|
|
30623
|
+
onStart,
|
|
30624
|
+
onEnd,
|
|
30625
|
+
onCancel
|
|
30626
|
+
} = options;
|
|
30627
|
+
|
|
30628
|
+
let startTime = null;
|
|
30629
|
+
let animationId = null;
|
|
30630
|
+
let cancelled = false;
|
|
30631
|
+
|
|
30632
|
+
function step(timestamp) {
|
|
30633
|
+
if (cancelled) {
|
|
30634
|
+
return;
|
|
30635
|
+
}
|
|
30636
|
+
|
|
30637
|
+
if (!startTime) {
|
|
30638
|
+
startTime = timestamp;
|
|
30639
|
+
if (onStart) {
|
|
30640
|
+
onStart();
|
|
30641
|
+
}
|
|
30642
|
+
}
|
|
30643
|
+
|
|
30644
|
+
const elapsed = timestamp - startTime;
|
|
30645
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
30646
|
+
const easedProgress = easeOut(progress);
|
|
30647
|
+
|
|
30648
|
+
const interpolatedValues = {};
|
|
30649
|
+
|
|
30650
|
+
for (const key in fromValues) {
|
|
30651
|
+
if (fromValues.hasOwnProperty(key)) {
|
|
30652
|
+
const from = fromValues[key];
|
|
30653
|
+
const to = toValues[key];
|
|
30654
|
+
|
|
30655
|
+
if (interpolateValue && key === 'zoom') {
|
|
30656
|
+
// Special interpolation for zoom to feel more natural
|
|
30657
|
+
// Exponential interpolation preserves relative zoom feel
|
|
30658
|
+
interpolatedValues[key] = from * Math.pow(to / from, easedProgress);
|
|
30659
|
+
} else {
|
|
30660
|
+
// Linear interpolation for other values
|
|
30661
|
+
interpolatedValues[key] = from + (to - from) * easedProgress;
|
|
30662
|
+
}
|
|
30663
|
+
}
|
|
30664
|
+
}
|
|
30665
|
+
|
|
30666
|
+
onStep(interpolatedValues);
|
|
30667
|
+
|
|
30668
|
+
if (progress < 1) {
|
|
30669
|
+
animationId = requestAnimationFrame(step);
|
|
30670
|
+
} else {
|
|
30671
|
+
if (onEnd) {
|
|
30672
|
+
onEnd();
|
|
30673
|
+
}
|
|
30674
|
+
}
|
|
30675
|
+
}
|
|
30676
|
+
|
|
30677
|
+
animationId = requestAnimationFrame(step);
|
|
30678
|
+
|
|
30679
|
+
// Return cancel function
|
|
30680
|
+
return function cancel() {
|
|
30681
|
+
cancelled = true;
|
|
30682
|
+
if (animationId) {
|
|
30683
|
+
cancelAnimationFrame(animationId);
|
|
30684
|
+
}
|
|
30685
|
+
if (onCancel) {
|
|
30686
|
+
onCancel();
|
|
30687
|
+
}
|
|
30688
|
+
};
|
|
30689
|
+
}
|
|
30690
|
+
|
|
30691
|
+
/**
|
|
30692
|
+
* Calculate smooth zoom delta with acceleration
|
|
30693
|
+
* Provides consistent zoom speed regardless of input device
|
|
30694
|
+
*
|
|
30695
|
+
* @param {number} currentScale - Current zoom scale
|
|
30696
|
+
* @param {number} delta - Input delta (wheel, gesture, etc)
|
|
30697
|
+
* @param {boolean} isTrackpad - Whether input is from trackpad
|
|
30698
|
+
* @returns {number} - Calculated zoom delta
|
|
30699
|
+
*/
|
|
30700
|
+
function calculateZoomDelta(currentScale, delta, isTrackpad) {
|
|
30701
|
+
// Normalize delta across different input devices
|
|
30702
|
+
let normalizedDelta = delta;
|
|
30703
|
+
|
|
30704
|
+
if (isTrackpad) {
|
|
30705
|
+
// Trackpad deltas are typically smaller and more frequent
|
|
30706
|
+
normalizedDelta = delta * 0.005; // Reduced from 0.01 for gentler zoom
|
|
30707
|
+
} else {
|
|
30708
|
+
// Mouse wheel deltas are larger and less frequent
|
|
30709
|
+
// Reduce zoom out speed more than zoom in
|
|
30710
|
+
normalizedDelta = delta > 0 ? 0.06 : -0.08; // Reduced from 0.1, asymmetric for gentler zoom out
|
|
30711
|
+
}
|
|
30712
|
+
|
|
30713
|
+
// Apply gentler acceleration based on current zoom level
|
|
30714
|
+
// Less aggressive acceleration to prevent rapid zoom out
|
|
30715
|
+
const acceleration = Math.max(0.7, Math.min(1.1, 1 / currentScale)); // Reduced from 0.5-1.2 to 0.7-1.1
|
|
30716
|
+
|
|
30717
|
+
return normalizedDelta * acceleration;
|
|
30718
|
+
}
|
|
30719
|
+
|
|
30720
|
+
/**
|
|
30721
|
+
* Gesture state management for consistent focal points
|
|
30722
|
+
*/
|
|
30723
|
+
const gestureState = {
|
|
30724
|
+
active: false,
|
|
30725
|
+
initialFocalPoint: null, // Will store workspace coordinates
|
|
30726
|
+
initialScale: 1,
|
|
30727
|
+
currentScale: 1,
|
|
30728
|
+
lastDistance: 0,
|
|
30729
|
+
scrollPosAtStart: null, // Store initial scroll position
|
|
30730
|
+
scaleFatorAtStart: 1 // Store initial scale factor
|
|
30731
|
+
};
|
|
30732
|
+
|
|
30733
|
+
/**
|
|
30734
|
+
* Start a zoom gesture with fixed focal point
|
|
30735
|
+
* @param {Array} focalPoint - [x, y] coordinates of focal point in workspace
|
|
30736
|
+
* @param {number} scale - Initial scale value
|
|
30737
|
+
* @param {Array} scrollPos - Current scroll position [x, y]
|
|
30738
|
+
* @param {number} currentScaleFactor - Current scale factor for coordinate conversion
|
|
30739
|
+
*/
|
|
30740
|
+
function startGesture(focalPoint, scale, scrollPos, currentScaleFactor) {
|
|
30741
|
+
gestureState.active = true;
|
|
30742
|
+
// Store the focal point in workspace coordinates for stability
|
|
30743
|
+
// This ensures the point remains fixed even if scroll changes due to canvas edge constraints
|
|
30744
|
+
if (focalPoint && scrollPos && currentScaleFactor) {
|
|
30745
|
+
gestureState.initialFocalPoint = [
|
|
30746
|
+
(scrollPos[0] + focalPoint[0]) / currentScaleFactor,
|
|
30747
|
+
(scrollPos[1] + focalPoint[1]) / currentScaleFactor
|
|
30748
|
+
];
|
|
30749
|
+
gestureState.scrollPosAtStart = [...scrollPos];
|
|
30750
|
+
gestureState.scaleFatorAtStart = currentScaleFactor;
|
|
30751
|
+
} else {
|
|
30752
|
+
gestureState.initialFocalPoint = focalPoint ? [...focalPoint] : null;
|
|
30753
|
+
}
|
|
30754
|
+
gestureState.initialScale = scale;
|
|
30755
|
+
gestureState.currentScale = scale;
|
|
30756
|
+
return gestureState;
|
|
30757
|
+
}
|
|
30758
|
+
|
|
30759
|
+
/**
|
|
30760
|
+
* Update gesture maintaining fixed focal point
|
|
30761
|
+
* @param {number} newScale - New scale value
|
|
30762
|
+
* @returns {Object} - Gesture state with fixed focal point
|
|
30763
|
+
*/
|
|
30764
|
+
function updateGesture(newScale) {
|
|
30765
|
+
if (!gestureState.active) {
|
|
30766
|
+
return null;
|
|
30767
|
+
}
|
|
30768
|
+
|
|
30769
|
+
gestureState.currentScale = newScale;
|
|
30770
|
+
|
|
30771
|
+
return {
|
|
30772
|
+
scale: newScale,
|
|
30773
|
+
focalPoint: gestureState.initialFocalPoint,
|
|
30774
|
+
active: gestureState.active
|
|
30775
|
+
};
|
|
30776
|
+
}
|
|
30777
|
+
|
|
30778
|
+
/**
|
|
30779
|
+
* End the current gesture
|
|
30780
|
+
*/
|
|
30781
|
+
function endGesture() {
|
|
30782
|
+
gestureState.active = false;
|
|
30783
|
+
gestureState.initialFocalPoint = null;
|
|
30784
|
+
gestureState.lastDistance = 0;
|
|
30785
|
+
}
|
|
30786
|
+
|
|
30787
|
+
/**
|
|
30788
|
+
* Check if a gesture is currently active
|
|
30789
|
+
*/
|
|
30790
|
+
function isGestureActive() {
|
|
30791
|
+
return gestureState.active;
|
|
30792
|
+
}
|
|
30793
|
+
|
|
30794
|
+
/**
|
|
30795
|
+
* Get the fixed focal point for the current gesture
|
|
30796
|
+
* @param {Array} currentScrollPos - Current scroll position [x, y]
|
|
30797
|
+
* @param {number} currentScaleFactor - Current scale factor
|
|
30798
|
+
* @returns {Array} - Focal point in screen coordinates or null
|
|
30799
|
+
*/
|
|
30800
|
+
function getGestureFocalPoint(currentScrollPos, currentScaleFactor) {
|
|
30801
|
+
if (!gestureState.initialFocalPoint) {
|
|
30802
|
+
return null;
|
|
30803
|
+
}
|
|
30804
|
+
|
|
30805
|
+
// If we stored workspace coordinates, convert back to screen coordinates
|
|
30806
|
+
if (gestureState.scrollPosAtStart && currentScrollPos && currentScaleFactor) {
|
|
30807
|
+
// Convert workspace coordinates back to current screen coordinates
|
|
30808
|
+
return [
|
|
30809
|
+
gestureState.initialFocalPoint[0] * currentScaleFactor - currentScrollPos[0],
|
|
30810
|
+
gestureState.initialFocalPoint[1] * currentScaleFactor - currentScrollPos[1]
|
|
30811
|
+
];
|
|
30812
|
+
}
|
|
30813
|
+
|
|
30814
|
+
return gestureState.initialFocalPoint;
|
|
30815
|
+
}
|
|
30816
|
+
|
|
30817
|
+
return {
|
|
30818
|
+
easeOut: easeOut,
|
|
30819
|
+
easeToValuesRAF: easeToValuesRAF,
|
|
30820
|
+
calculateZoomDelta: calculateZoomDelta,
|
|
30821
|
+
gestureState: gestureState,
|
|
30822
|
+
startGesture: startGesture,
|
|
30823
|
+
updateGesture: updateGesture,
|
|
30824
|
+
endGesture: endGesture,
|
|
30825
|
+
isGestureActive: isGestureActive,
|
|
30826
|
+
getGestureFocalPoint: getGestureFocalPoint
|
|
30827
|
+
};
|
|
30828
|
+
})();;RED.view.annotations = (function() {
|
|
29705
30829
|
|
|
29706
30830
|
var annotations = {};
|
|
29707
30831
|
|
|
@@ -29901,139 +31025,202 @@ RED.view = (function() {
|
|
|
29901
31025
|
**/
|
|
29902
31026
|
|
|
29903
31027
|
|
|
29904
|
-
|
|
29905
|
-
|
|
29906
|
-
|
|
29907
|
-
|
|
29908
|
-
|
|
29909
|
-
|
|
29910
|
-
|
|
29911
|
-
|
|
29912
|
-
|
|
29913
|
-
|
|
29914
|
-
|
|
29915
|
-
|
|
29916
|
-
|
|
29917
|
-
|
|
29918
|
-
|
|
29919
|
-
|
|
29920
|
-
|
|
29921
|
-
|
|
29922
|
-
|
|
29923
|
-
|
|
29924
|
-
|
|
29925
|
-
|
|
29926
|
-
|
|
29927
|
-
|
|
29928
|
-
|
|
29929
|
-
|
|
29930
|
-
|
|
29931
|
-
|
|
29932
|
-
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
|
|
29939
|
-
|
|
29940
|
-
|
|
29941
|
-
|
|
29942
|
-
|
|
29943
|
-
|
|
29944
|
-
|
|
29945
|
-
|
|
29946
|
-
|
|
29947
|
-
|
|
29948
|
-
|
|
29949
|
-
|
|
29950
|
-
|
|
29951
|
-
|
|
29952
|
-
|
|
29953
|
-
|
|
29954
|
-
|
|
29955
|
-
|
|
29956
|
-
|
|
29957
|
-
|
|
29958
|
-
|
|
29959
|
-
|
|
29960
|
-
|
|
29961
|
-
|
|
29962
|
-
|
|
29963
|
-
|
|
29964
|
-
|
|
29965
|
-
|
|
29966
|
-
|
|
29967
|
-
|
|
29968
|
-
|
|
29969
|
-
|
|
29970
|
-
|
|
29971
|
-
|
|
29972
|
-
|
|
29973
|
-
|
|
29974
|
-
|
|
29975
|
-
|
|
29976
|
-
|
|
29977
|
-
|
|
29978
|
-
|
|
29979
|
-
|
|
29980
|
-
|
|
29981
|
-
|
|
29982
|
-
|
|
29983
|
-
|
|
29984
|
-
|
|
29985
|
-
|
|
29986
|
-
|
|
29987
|
-
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
|
|
29996
|
-
|
|
29997
|
-
|
|
29998
|
-
|
|
29999
|
-
|
|
30000
|
-
|
|
30001
|
-
|
|
30002
|
-
|
|
30003
|
-
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30007
|
-
|
|
30008
|
-
|
|
30009
|
-
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
|
|
30013
|
-
|
|
30014
|
-
|
|
30015
|
-
|
|
30016
|
-
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30021
|
-
|
|
30022
|
-
|
|
30023
|
-
|
|
30024
|
-
|
|
30025
|
-
|
|
30026
|
-
|
|
30027
|
-
|
|
30028
|
-
|
|
30029
|
-
|
|
30030
|
-
|
|
31028
|
+
RED.view.navigator = (function() {
|
|
31029
|
+
var nav_scale = 50;
|
|
31030
|
+
var nav_width = 8000/nav_scale;
|
|
31031
|
+
var nav_height = 8000/nav_scale;
|
|
31032
|
+
var navContainer;
|
|
31033
|
+
var navBox;
|
|
31034
|
+
var navBorder;
|
|
31035
|
+
var navVis;
|
|
31036
|
+
var scrollPos;
|
|
31037
|
+
var scaleFactor;
|
|
31038
|
+
var chartSize;
|
|
31039
|
+
var dimensions;
|
|
31040
|
+
var isDragging;
|
|
31041
|
+
var isShowing = false;
|
|
31042
|
+
var toggleTimeout;
|
|
31043
|
+
var autoHideTimeout;
|
|
31044
|
+
var isManuallyToggled = false;
|
|
31045
|
+
var isTemporaryShow = false;
|
|
31046
|
+
function refreshNodes() {
|
|
31047
|
+
if (!isShowing) {
|
|
31048
|
+
return;
|
|
31049
|
+
}
|
|
31050
|
+
var navNode = navVis.selectAll(".red-ui-navigator-node").data(RED.view.getActiveNodes(),function(d){return d.id});
|
|
31051
|
+
navNode.exit().remove();
|
|
31052
|
+
navNode.enter().insert("rect")
|
|
31053
|
+
.attr('class','red-ui-navigator-node')
|
|
31054
|
+
.attr("pointer-events", "none");
|
|
31055
|
+
navNode.each(function(d) {
|
|
31056
|
+
d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale })
|
|
31057
|
+
.attr("y",function(d) { return (d.y-d.h/2)/nav_scale })
|
|
31058
|
+
.attr("width",function(d) { return Math.max(9,d.w/nav_scale) })
|
|
31059
|
+
.attr("height",function(d) { return Math.max(3,d.h/nav_scale) })
|
|
31060
|
+
.attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def);})
|
|
31061
|
+
});
|
|
31062
|
+
}
|
|
31063
|
+
function onScroll() {
|
|
31064
|
+
if (!isDragging) {
|
|
31065
|
+
resizeNavBorder();
|
|
31066
|
+
}
|
|
31067
|
+
}
|
|
31068
|
+
function resizeNavBorder() {
|
|
31069
|
+
if (navBorder) {
|
|
31070
|
+
scaleFactor = RED.view.scale();
|
|
31071
|
+
chartSize = [ $("#red-ui-workspace-chart").width(), $("#red-ui-workspace-chart").height()];
|
|
31072
|
+
scrollPos = [$("#red-ui-workspace-chart").scrollLeft(),$("#red-ui-workspace-chart").scrollTop()];
|
|
31073
|
+
// Convert scroll position (in scaled pixels) to workspace coordinates, then to minimap coordinates
|
|
31074
|
+
// scrollPos is in scaled canvas pixels, divide by scaleFactor to get workspace coords
|
|
31075
|
+
navBorder.attr('x',scrollPos[0]/scaleFactor/nav_scale)
|
|
31076
|
+
.attr('y',scrollPos[1]/scaleFactor/nav_scale)
|
|
31077
|
+
.attr('width',chartSize[0]/nav_scale/scaleFactor)
|
|
31078
|
+
.attr('height',chartSize[1]/nav_scale/scaleFactor)
|
|
31079
|
+
}
|
|
31080
|
+
}
|
|
31081
|
+
function show () {
|
|
31082
|
+
if (!isShowing) {
|
|
31083
|
+
isShowing = true;
|
|
31084
|
+
clearTimeout(autoHideTimeout);
|
|
31085
|
+
$("#red-ui-view-navigate").addClass("selected");
|
|
31086
|
+
resizeNavBorder();
|
|
31087
|
+
refreshNodes();
|
|
31088
|
+
$("#red-ui-workspace-chart").on("scroll",onScroll);
|
|
31089
|
+
navContainer.addClass('red-ui-navigator-container');
|
|
31090
|
+
navContainer.show();
|
|
31091
|
+
clearTimeout(toggleTimeout)
|
|
31092
|
+
toggleTimeout = setTimeout(function() {
|
|
31093
|
+
navContainer.addClass('red-ui-navigator-visible');
|
|
31094
|
+
}, 10);
|
|
31095
|
+
}
|
|
31096
|
+
}
|
|
31097
|
+
function hide () {
|
|
31098
|
+
if (isShowing) {
|
|
31099
|
+
isShowing = false;
|
|
31100
|
+
isTemporaryShow = false;
|
|
31101
|
+
isManuallyToggled = false;
|
|
31102
|
+
clearTimeout(autoHideTimeout);
|
|
31103
|
+
navContainer.removeClass('red-ui-navigator-visible');
|
|
31104
|
+
clearTimeout(toggleTimeout)
|
|
31105
|
+
toggleTimeout = setTimeout(function() {
|
|
31106
|
+
navContainer.hide();
|
|
31107
|
+
}, 300);
|
|
31108
|
+
$("#red-ui-workspace-chart").off("scroll",onScroll);
|
|
31109
|
+
$("#red-ui-view-navigate").removeClass("selected");
|
|
31110
|
+
}
|
|
31111
|
+
}
|
|
31112
|
+
function toggle() {
|
|
31113
|
+
if (!isShowing) {
|
|
31114
|
+
isManuallyToggled = true;
|
|
31115
|
+
show()
|
|
31116
|
+
} else {
|
|
31117
|
+
isManuallyToggled = false;
|
|
31118
|
+
hide()
|
|
31119
|
+
}
|
|
31120
|
+
}
|
|
31121
|
+
function setupAutoHide () {
|
|
31122
|
+
clearTimeout(autoHideTimeout);
|
|
31123
|
+
autoHideTimeout = setTimeout(function() {
|
|
31124
|
+
hide()
|
|
31125
|
+
}, 2000)
|
|
31126
|
+
}
|
|
31127
|
+
function showTemporary() {
|
|
31128
|
+
if (!isManuallyToggled) {
|
|
31129
|
+
isTemporaryShow = true
|
|
31130
|
+
clearTimeout(autoHideTimeout);
|
|
31131
|
+
show()
|
|
31132
|
+
setupAutoHide()
|
|
31133
|
+
}
|
|
31134
|
+
}
|
|
31135
|
+
return {
|
|
31136
|
+
init: function() {
|
|
31137
|
+
$(window).on("resize", resizeNavBorder);
|
|
31138
|
+
RED.events.on("sidebar:resize",resizeNavBorder);
|
|
31139
|
+
RED.actions.add("core:toggle-navigator",toggle);
|
|
31140
|
+
navContainer = $('<div>').css({
|
|
31141
|
+
"position":"absolute",
|
|
31142
|
+
"bottom":$("#red-ui-workspace-footer").height(),
|
|
31143
|
+
"right":0,
|
|
31144
|
+
zIndex: 1
|
|
31145
|
+
}).addClass('red-ui-navigator-container').appendTo("#red-ui-workspace").hide();
|
|
31146
|
+
navBox = d3.select(navContainer[0])
|
|
31147
|
+
.append("svg:svg")
|
|
31148
|
+
.attr("width", nav_width)
|
|
31149
|
+
.attr("height", nav_height)
|
|
31150
|
+
.attr("pointer-events", "all")
|
|
31151
|
+
.attr("id","red-ui-navigator-canvas")
|
|
31152
|
+
navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({
|
|
31153
|
+
fill:"none",
|
|
31154
|
+
stroke:"none",
|
|
31155
|
+
pointerEvents:"all"
|
|
31156
|
+
}).on("mousedown", function() {
|
|
31157
|
+
// Update these in case they have changed
|
|
31158
|
+
scaleFactor = RED.view.scale();
|
|
31159
|
+
chartSize = [ $("#red-ui-workspace-chart").width(), $("#red-ui-workspace-chart").height()];
|
|
31160
|
+
dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor];
|
|
31161
|
+
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
|
|
31162
|
+
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
|
|
31163
|
+
navBorder.attr('x',newX).attr('y',newY);
|
|
31164
|
+
isDragging = true;
|
|
31165
|
+
$("#red-ui-workspace-chart").scrollLeft(newX*nav_scale*scaleFactor);
|
|
31166
|
+
$("#red-ui-workspace-chart").scrollTop(newY*nav_scale*scaleFactor);
|
|
31167
|
+
}).on("mousemove", function() {
|
|
31168
|
+
if (!isDragging) { return }
|
|
31169
|
+
if (d3.event.buttons === 0) {
|
|
31170
|
+
isDragging = false;
|
|
31171
|
+
return;
|
|
31172
|
+
}
|
|
31173
|
+
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
|
|
31174
|
+
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
|
|
31175
|
+
navBorder.attr('x',newX).attr('y',newY);
|
|
31176
|
+
$("#red-ui-workspace-chart").scrollLeft(newX*nav_scale*scaleFactor);
|
|
31177
|
+
$("#red-ui-workspace-chart").scrollTop(newY*nav_scale*scaleFactor);
|
|
31178
|
+
}).on("mouseup", function() {
|
|
31179
|
+
isDragging = false;
|
|
31180
|
+
}).on("mouseenter", function () {
|
|
31181
|
+
if (isTemporaryShow) {
|
|
31182
|
+
// If user hovers over the minimap while it's temporarily shown, keep it shown
|
|
31183
|
+
clearTimeout(autoHideTimeout);
|
|
31184
|
+
}
|
|
31185
|
+
}).on("mouseleave", function () {
|
|
31186
|
+
if (isTemporaryShow) {
|
|
31187
|
+
// Restart the auto-hide timer after mouse leaves the minimap
|
|
31188
|
+
setupAutoHide()
|
|
31189
|
+
}
|
|
31190
|
+
})
|
|
31191
|
+
navBorder = navBox.append("rect").attr("class","red-ui-navigator-border")
|
|
31192
|
+
navVis = navBox.append("svg:g")
|
|
31193
|
+
RED.statusBar.add({
|
|
31194
|
+
id: "view-navigator",
|
|
31195
|
+
align: "right",
|
|
31196
|
+
element: $('<button class="red-ui-footer-button-toggle single" id="red-ui-view-navigate"><i class="fa fa-map-o"></i></button>')
|
|
31197
|
+
})
|
|
30031
31198
|
|
|
30032
31199
|
$("#red-ui-view-navigate").on("click", function(evt) {
|
|
30033
31200
|
evt.preventDefault();
|
|
30034
31201
|
toggle();
|
|
30035
31202
|
})
|
|
30036
31203
|
RED.popover.tooltip($("#red-ui-view-navigate"),RED._('actions.toggle-navigator'),'core:toggle-navigator');
|
|
31204
|
+
|
|
31205
|
+
// Listen for canvas interactions to show minimap temporarily
|
|
31206
|
+
// Only show on actual pan/zoom navigation, not selection changes
|
|
31207
|
+
// RED.events.on("view:navigate", function() {
|
|
31208
|
+
// showTemporary();
|
|
31209
|
+
// });
|
|
31210
|
+
|
|
31211
|
+
// Show minimap briefly when workspace changes (includes initial load)
|
|
31212
|
+
// RED.events.on("workspace:change", function(event) {
|
|
31213
|
+
// // Only show if there's an active workspace with nodes
|
|
31214
|
+
// if (event.workspace && RED.nodes.getWorkspaceOrder().length > 0) {
|
|
31215
|
+
// // Small delay to ensure nodes are rendered
|
|
31216
|
+
// setTimeout(function() {
|
|
31217
|
+
// var activeNodes = RED.nodes.filterNodes({z: event.workspace});
|
|
31218
|
+
// if (activeNodes.length > 0) {
|
|
31219
|
+
// showTemporary();
|
|
31220
|
+
// }
|
|
31221
|
+
// }, 100);
|
|
31222
|
+
// }
|
|
31223
|
+
// });
|
|
30037
31224
|
},
|
|
30038
31225
|
refresh: refreshNodes,
|
|
30039
31226
|
resize: resizeNavBorder,
|
|
@@ -31501,19 +32688,56 @@ RED.view.tools = (function() {
|
|
|
31501
32688
|
* limitations under the License.
|
|
31502
32689
|
**/
|
|
31503
32690
|
RED.sidebar = (function() {
|
|
32691
|
+
const sidebars = {
|
|
32692
|
+
primary: {
|
|
32693
|
+
id: 'primary',
|
|
32694
|
+
direction: 'right',
|
|
32695
|
+
menuToggle: 'menu-item-sidebar',
|
|
32696
|
+
minimumWidth: 180,
|
|
32697
|
+
maximumWidth: 800,
|
|
32698
|
+
defaultWidth: 300
|
|
32699
|
+
},
|
|
32700
|
+
secondary: {
|
|
32701
|
+
id: 'secondary',
|
|
32702
|
+
direction: 'left',
|
|
32703
|
+
menuToggle: 'menu-item-palette',
|
|
32704
|
+
minimumWidth: 180,
|
|
32705
|
+
maximumWidth: 800,
|
|
32706
|
+
// Make LH side slightly narrower by default as its the palette that doesn't require a lot of width
|
|
32707
|
+
defaultWidth: 210
|
|
32708
|
+
}
|
|
32709
|
+
}
|
|
32710
|
+
const defaultSidebarConfiguration = {
|
|
32711
|
+
primary: ['info','debug','help','config','context'],
|
|
32712
|
+
secondary: ['palette']
|
|
32713
|
+
}
|
|
32714
|
+
|
|
32715
|
+
const knownTabs = {};
|
|
32716
|
+
|
|
32717
|
+
function exportSidebarState () {
|
|
32718
|
+
const state = {
|
|
32719
|
+
primary: [],
|
|
32720
|
+
secondary: []
|
|
32721
|
+
}
|
|
32722
|
+
sidebars.primary.tabBar.children('button').each(function() {
|
|
32723
|
+
const tabId = $(this).attr('data-tab-id');
|
|
32724
|
+
state.primary.push(tabId);
|
|
32725
|
+
})
|
|
32726
|
+
sidebars.secondary.tabBar.children('button').each(function() {
|
|
32727
|
+
const tabId = $(this).attr('data-tab-id');
|
|
32728
|
+
state.secondary.push(tabId);
|
|
32729
|
+
})
|
|
32730
|
+
RED.settings.set('editor.sidebar.state', state)
|
|
32731
|
+
}
|
|
31504
32732
|
|
|
31505
|
-
//$('#sidebar').tabs();
|
|
31506
|
-
var sidebar_tabs;
|
|
31507
|
-
var knownTabs = {};
|
|
31508
32733
|
|
|
31509
32734
|
// We store the current sidebar tab id in localStorage as 'last-sidebar-tab'
|
|
31510
32735
|
// This is restored when the editor is reloaded.
|
|
31511
|
-
// We use
|
|
32736
|
+
// We use sidebars.primary.tabs.onchange to update localStorage. However that will
|
|
31512
32737
|
// also get triggered when the first tab gets added to the tabs - typically
|
|
31513
32738
|
// the 'info' tab. So we use the following variable to store the retrieved
|
|
31514
32739
|
// value from localStorage before we start adding the actual tabs
|
|
31515
|
-
|
|
31516
|
-
|
|
32740
|
+
let lastSessionSelectedTabs = {}
|
|
31517
32741
|
|
|
31518
32742
|
function addTab(title,content,closeable,visible) {
|
|
31519
32743
|
var options;
|
|
@@ -31530,10 +32754,34 @@ RED.sidebar = (function() {
|
|
|
31530
32754
|
} else if (typeof title === "object") {
|
|
31531
32755
|
options = title;
|
|
31532
32756
|
}
|
|
32757
|
+
options.target = options.target || 'primary';
|
|
32758
|
+
let targetTabButtonIndex = -1 // Append to end by default
|
|
31533
32759
|
|
|
32760
|
+
// Check the saved sidebar state to see if this tab should be added to the primary or secondary sidebar
|
|
32761
|
+
const savedState = RED.settings.get('editor.sidebar.state', defaultSidebarConfiguration)
|
|
32762
|
+
if (savedState) {
|
|
32763
|
+
let targetSidebar = null
|
|
32764
|
+
let sidebarState
|
|
32765
|
+
if (savedState.secondary.includes(options.id)) {
|
|
32766
|
+
options.target = 'secondary'
|
|
32767
|
+
sidebarState = savedState.secondary
|
|
32768
|
+
targetSidebar = sidebars.secondary
|
|
32769
|
+
} else if (savedState.primary.includes(options.id)) {
|
|
32770
|
+
options.target = 'primary'
|
|
32771
|
+
sidebarState = savedState.primary
|
|
32772
|
+
targetSidebar = sidebars.primary
|
|
32773
|
+
}
|
|
32774
|
+
if (targetSidebar) {
|
|
32775
|
+
// This tab was found in the saved sidebar state. Now find the target position for the tab button
|
|
32776
|
+
targetTabButtonIndex = sidebarState.indexOf(options.id)
|
|
32777
|
+
}
|
|
32778
|
+
}
|
|
32779
|
+
|
|
32780
|
+
|
|
32781
|
+
const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary;
|
|
31534
32782
|
delete options.closeable;
|
|
31535
32783
|
|
|
31536
|
-
options.wrapper = $('<div>',{style:"height:100%"}).appendTo(
|
|
32784
|
+
options.wrapper = $('<div>',{style:"height:100%"}).appendTo(targetSidebar.content)
|
|
31537
32785
|
options.wrapper.append(options.content);
|
|
31538
32786
|
options.wrapper.hide();
|
|
31539
32787
|
|
|
@@ -31542,11 +32790,12 @@ RED.sidebar = (function() {
|
|
|
31542
32790
|
}
|
|
31543
32791
|
|
|
31544
32792
|
if (options.toolbar) {
|
|
31545
|
-
|
|
32793
|
+
targetSidebar.footer.append(options.toolbar);
|
|
31546
32794
|
$(options.toolbar).hide();
|
|
31547
32795
|
}
|
|
31548
32796
|
var id = options.id;
|
|
31549
32797
|
|
|
32798
|
+
// console.log('menu', options.id, options.name)
|
|
31550
32799
|
RED.menu.addItem("menu-item-view-menu",{
|
|
31551
32800
|
id:"menu-item-view-menu-"+options.id,
|
|
31552
32801
|
label:options.name,
|
|
@@ -31559,208 +32808,315 @@ RED.sidebar = (function() {
|
|
|
31559
32808
|
options.iconClass = options.iconClass || "fa fa-square-o"
|
|
31560
32809
|
|
|
31561
32810
|
knownTabs[options.id] = options;
|
|
32811
|
+
options.tabButton = $('<button></button>')
|
|
32812
|
+
// Insert the tab button at the correct index
|
|
32813
|
+
if (targetTabButtonIndex === -1 || targetTabButtonIndex >= targetSidebar.tabBar.children().length) {
|
|
32814
|
+
// Append to end
|
|
32815
|
+
options.tabButton = $('<button></button>').appendTo(targetSidebar.tabBar);
|
|
32816
|
+
} else {
|
|
32817
|
+
// Insert before the item at targetTabButtonIndex
|
|
32818
|
+
options.tabButton = $('<button></button>').insertBefore(targetSidebar.tabBar.children().eq(targetTabButtonIndex));
|
|
32819
|
+
}
|
|
32820
|
+
options.tabButton.attr('data-tab-id', options.id)
|
|
31562
32821
|
|
|
31563
|
-
|
|
31564
|
-
|
|
32822
|
+
options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action);
|
|
32823
|
+
if (options.icon) {
|
|
32824
|
+
$('<i>',{class: 'red-ui-sidebar-tab-icon', style:"mask-image: url("+options.icon+"); -webkit-mask-image: url("+options.icon+");"}).appendTo(options.tabButton);
|
|
32825
|
+
} else if (options.iconClass) {
|
|
32826
|
+
$('<i>',{class:options.iconClass}).appendTo(options.tabButton);
|
|
32827
|
+
}
|
|
32828
|
+
options.tabButton.on('mouseup', function(evt) {
|
|
32829
|
+
if (draggingTabButton) {
|
|
32830
|
+
draggingTabButton = false
|
|
32831
|
+
return
|
|
32832
|
+
}
|
|
32833
|
+
const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary;
|
|
32834
|
+
if (targetSidebar.activeTab === options.id && RED.menu.isSelected(targetSidebar.menuToggle)) {
|
|
32835
|
+
RED.menu.setSelected(targetSidebar.menuToggle, false);
|
|
32836
|
+
} else {
|
|
32837
|
+
RED.sidebar.show(options.id)
|
|
32838
|
+
}
|
|
32839
|
+
})
|
|
32840
|
+
if (targetSidebar.content.children().length === 1) {
|
|
32841
|
+
RED.sidebar.show(options.id)
|
|
31565
32842
|
}
|
|
31566
32843
|
}
|
|
31567
32844
|
|
|
31568
32845
|
function removeTab(id) {
|
|
31569
|
-
|
|
31570
|
-
|
|
31571
|
-
|
|
31572
|
-
knownTabs[id].footer
|
|
32846
|
+
if (knownTabs[id]) {
|
|
32847
|
+
const targetSidebar = knownTabs[id].target === 'secondary' ? sidebars.secondary : sidebars.primary;
|
|
32848
|
+
$(knownTabs[id].wrapper).remove();
|
|
32849
|
+
if (knownTabs[id].footer) {
|
|
32850
|
+
knownTabs[id].footer.remove();
|
|
32851
|
+
}
|
|
32852
|
+
targetSidebar.tabBar.find('button[data-tab-id="'+id+'"]').remove()
|
|
32853
|
+
RED.menu.removeItem("menu-item-view-menu-"+id);
|
|
32854
|
+
if (knownTabs[id].onremove) {
|
|
32855
|
+
knownTabs[id].onremove.call(knownTabs[id]);
|
|
32856
|
+
}
|
|
32857
|
+
delete knownTabs[id];
|
|
32858
|
+
const firstTab = targetSidebar.tabBar.find('button').first().attr('data-tab-id');
|
|
32859
|
+
if (firstTab) {
|
|
32860
|
+
RED.sidebar.show(firstTab);
|
|
32861
|
+
}
|
|
31573
32862
|
}
|
|
31574
|
-
delete knownTabs[id];
|
|
31575
|
-
RED.menu.removeItem("menu-item-view-menu-"+id);
|
|
31576
32863
|
}
|
|
31577
32864
|
|
|
31578
|
-
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31582
|
-
|
|
31583
|
-
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
31592
|
-
|
|
31593
|
-
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
|
|
31597
|
-
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
|
|
31623
|
-
|
|
31624
|
-
|
|
31625
|
-
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
32865
|
+
function moveTab(id, srcSidebar, targetSidebar) {
|
|
32866
|
+
const options = knownTabs[id];
|
|
32867
|
+
options.target = targetSidebar.id;
|
|
32868
|
+
$(options.wrapper).appendTo(targetSidebar.content);
|
|
32869
|
+
if (options.toolbar) {
|
|
32870
|
+
targetSidebar.footer.append(options.toolbar);
|
|
32871
|
+
}
|
|
32872
|
+
// Reset the tooltip so its left/right direction is recalculated
|
|
32873
|
+
options.tabButtonTooltip.delete()
|
|
32874
|
+
options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action);
|
|
32875
|
+
|
|
32876
|
+
if (targetSidebar.content.children().length === 1) {
|
|
32877
|
+
RED.sidebar.show(options.id)
|
|
32878
|
+
}
|
|
32879
|
+
if (srcSidebar.content.children().length === 0) {
|
|
32880
|
+
RED.menu.setSelected(srcSidebar.menuToggle, false);
|
|
32881
|
+
}
|
|
32882
|
+
}
|
|
32883
|
+
|
|
32884
|
+
let draggingTabButton = false
|
|
32885
|
+
function setupSidebarTabs(sidebar) {
|
|
32886
|
+
const tabBar = $('<div class="red-ui-sidebar-tab-bar"></div>').addClass('red-ui-sidebar-' + sidebar.direction);
|
|
32887
|
+
tabBar.attr('id', sidebar.container.attr('id') + '-tab-bar')
|
|
32888
|
+
tabBar.data('sidebar', sidebar.id)
|
|
32889
|
+
if (sidebar.direction === 'right') {
|
|
32890
|
+
tabBar.insertAfter(sidebar.container);
|
|
32891
|
+
} else if (sidebar.direction === 'left') {
|
|
32892
|
+
tabBar.insertBefore(sidebar.container);
|
|
32893
|
+
}
|
|
32894
|
+
tabBar.sortable({
|
|
32895
|
+
distance: 10,
|
|
32896
|
+
cancel: false,
|
|
32897
|
+
placeholder: "red-ui-sidebar-tab-bar-button-placeholder",
|
|
32898
|
+
connectWith: ".red-ui-sidebar-tab-bar",
|
|
32899
|
+
start: function(event, ui) {
|
|
32900
|
+
// Remove the tooltip so it doesn't display unexpectedly whilst dragging
|
|
32901
|
+
const tabId = ui.item.attr('data-tab-id');
|
|
32902
|
+
const options = knownTabs[tabId];
|
|
32903
|
+
options.tabButtonTooltip.delete()
|
|
32904
|
+
draggingTabButton = true
|
|
32905
|
+
tabBar.css('z-index','inherit')
|
|
32906
|
+
},
|
|
32907
|
+
stop: function(event, ui) {
|
|
32908
|
+
// Restore the tooltip
|
|
32909
|
+
const tabId = ui.item.attr('data-tab-id');
|
|
32910
|
+
const options = knownTabs[tabId];
|
|
32911
|
+
options.tabButtonTooltip.delete()
|
|
32912
|
+
options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action);
|
|
32913
|
+
// Save the sidebar state
|
|
32914
|
+
exportSidebarState()
|
|
32915
|
+
tabBar.css('z-index','')
|
|
32916
|
+
},
|
|
32917
|
+
receive: function(event, ui) {
|
|
32918
|
+
// Tab has been moved from one sidebar to another
|
|
32919
|
+
const src = sidebars[ui.sender.data('sidebar')]
|
|
32920
|
+
const dest = sidebars[$(this).data('sidebar')]
|
|
32921
|
+
const tabId = ui.item.attr('data-tab-id');
|
|
32922
|
+
moveTab(tabId, src, dest)
|
|
32923
|
+
if (ui.item.hasClass('selected')) {
|
|
32924
|
+
const firstTab = src.tabBar.find('button').first().attr('data-tab-id');
|
|
32925
|
+
if (firstTab) {
|
|
32926
|
+
RED.sidebar.show(firstTab);
|
|
31633
32927
|
}
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
32928
|
+
}
|
|
32929
|
+
RED.sidebar.show(tabId)
|
|
32930
|
+
}
|
|
32931
|
+
})
|
|
32932
|
+
// $(window).on("resize", function () {
|
|
32933
|
+
// const lastChild = tabBar.children().last();
|
|
32934
|
+
// if (lastChild.length > 0) {
|
|
32935
|
+
// const tabBarHeight = tabBar.height();
|
|
32936
|
+
// const lastChildBottom = lastChild.position().top + lastChild.outerHeight();
|
|
32937
|
+
// if (lastChildBottom > tabBarHeight) {
|
|
32938
|
+
// console.log('overflow')
|
|
32939
|
+
// }
|
|
32940
|
+
// }
|
|
32941
|
+
// })
|
|
32942
|
+
return tabBar
|
|
32943
|
+
}
|
|
32944
|
+
function setupSidebarSeparator(sidebar) {
|
|
32945
|
+
const separator = $('<div class="red-ui-sidebar-separator"></div>');
|
|
32946
|
+
separator.attr('id', sidebar.container.attr('id') + '-separator')
|
|
32947
|
+
$('<div class="red-ui-sidebar-shade hide"></div>').appendTo(separator);
|
|
32948
|
+
$('<div class="red-ui-sidebar-separator-handle"></div>').appendTo(separator);
|
|
32949
|
+
let scaleFactor = 1;
|
|
32950
|
+
if (sidebar.direction === 'right') {
|
|
32951
|
+
separator.insertBefore(sidebar.container);
|
|
32952
|
+
} else if (sidebar.direction === 'left') {
|
|
32953
|
+
scaleFactor = -1;
|
|
32954
|
+
separator.insertAfter(sidebar.container);
|
|
32955
|
+
}
|
|
32956
|
+
// Track sidebar state whilst dragging
|
|
32957
|
+
const sidebarSeparator = {}
|
|
32958
|
+
separator.draggable({
|
|
32959
|
+
axis: "x",
|
|
32960
|
+
start:function(event,ui) {
|
|
32961
|
+
sidebarSeparator.closing = false;
|
|
32962
|
+
sidebarSeparator.opening = false;
|
|
32963
|
+
// var winWidth = $("#red-ui-editor").width();
|
|
32964
|
+
sidebarSeparator.start = ui.position.left;
|
|
32965
|
+
sidebarSeparator.width = sidebar.container.width();
|
|
32966
|
+
sidebarSeparator.chartWidth = $("#red-ui-workspace").width();
|
|
32967
|
+
sidebarSeparator.dragging = true;
|
|
32968
|
+
|
|
32969
|
+
if (!RED.menu.isSelected(sidebar.menuToggle)) {
|
|
32970
|
+
sidebarSeparator.opening = true;
|
|
32971
|
+
sidebar.container.width(0);
|
|
32972
|
+
RED.menu.setSelected(sidebar.menuToggle,true);
|
|
31656
32973
|
RED.events.emit("sidebar:resize");
|
|
31657
32974
|
}
|
|
31658
|
-
|
|
32975
|
+
sidebarSeparator.width = sidebar.container.width();
|
|
32976
|
+
},
|
|
32977
|
+
drag: function(event,ui) {
|
|
32978
|
+
var d = scaleFactor * (ui.position.left-sidebarSeparator.start);
|
|
31659
32979
|
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
31663
|
-
|
|
31664
|
-
|
|
31665
|
-
|
|
31666
|
-
|
|
31667
|
-
if (
|
|
31668
|
-
|
|
32980
|
+
var newSidebarWidth = sidebarSeparator.width - d;
|
|
32981
|
+
if (newSidebarWidth > sidebar.maximumWidth) {
|
|
32982
|
+
newSidebarWidth = sidebar.maximumWidth;
|
|
32983
|
+
d = sidebarSeparator.width - sidebar.maximumWidth;
|
|
32984
|
+
ui.position.left = sidebarSeparator.start + scaleFactor * d;
|
|
32985
|
+
}
|
|
32986
|
+
|
|
32987
|
+
if (newSidebarWidth > sidebar.minimumWidth) {
|
|
32988
|
+
if (sidebarSeparator.chartWidth + d < 200) {
|
|
32989
|
+
// Chart is now too small, but we have room to resize the sidebar
|
|
32990
|
+
d += (200 - (sidebarSeparator.chartWidth + d));
|
|
32991
|
+
newSidebarWidth = sidebarSeparator.width - d;
|
|
32992
|
+
ui.position.left = sidebarSeparator.start + scaleFactor * d;
|
|
32993
|
+
}
|
|
32994
|
+
} else if (newSidebarWidth < sidebar.minimumWidth) {
|
|
32995
|
+
if (newSidebarWidth > 100) {
|
|
32996
|
+
newSidebarWidth = sidebar.minimumWidth
|
|
32997
|
+
sidebarSeparator.closing = false
|
|
32998
|
+
} else {
|
|
32999
|
+
newSidebarWidth = 0
|
|
33000
|
+
sidebarSeparator.closing = true
|
|
33001
|
+
}
|
|
31669
33002
|
} else {
|
|
31670
|
-
|
|
33003
|
+
sidebarSeparator.closing = false
|
|
31671
33004
|
}
|
|
31672
|
-
|
|
31673
|
-
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
33005
|
+
sidebar.container.width(newSidebarWidth);
|
|
33006
|
+
ui.position.left -= scaleFactor * d
|
|
33007
|
+
|
|
33008
|
+
// sidebar.tabs.resize();
|
|
33009
|
+
RED.events.emit("sidebar:resize");
|
|
33010
|
+
},
|
|
33011
|
+
stop:function(event,ui) {
|
|
33012
|
+
sidebarSeparator.dragging = false;
|
|
33013
|
+
if (sidebarSeparator.closing) {
|
|
33014
|
+
sidebar.container.removeClass("closing");
|
|
33015
|
+
if (sidebar.menuToggle) {
|
|
33016
|
+
RED.menu.setSelected(sidebar.menuToggle,false);
|
|
33017
|
+
}
|
|
33018
|
+
sidebar.container.hide()
|
|
33019
|
+
sidebar.separator.hide()
|
|
33020
|
+
if (sidebar.container.width() < sidebar.minimumWidth) {
|
|
33021
|
+
sidebar.container.width(sidebar.defaultWidth);
|
|
33022
|
+
}
|
|
33023
|
+
}
|
|
33024
|
+
RED.events.emit("sidebar:resize");
|
|
31679
33025
|
}
|
|
31680
33026
|
});
|
|
33027
|
+
return separator
|
|
31681
33028
|
}
|
|
31682
33029
|
|
|
31683
|
-
function toggleSidebar(state) {
|
|
33030
|
+
function toggleSidebar(sidebar, state) {
|
|
31684
33031
|
if (!state) {
|
|
31685
|
-
|
|
33032
|
+
sidebar.container.hide()
|
|
33033
|
+
sidebar.separator.hide()
|
|
33034
|
+
sidebar.tabBar.find('button').removeClass('selected')
|
|
31686
33035
|
} else {
|
|
31687
|
-
|
|
31688
|
-
|
|
33036
|
+
sidebar.container.show()
|
|
33037
|
+
sidebar.separator.show()
|
|
31689
33038
|
}
|
|
31690
33039
|
RED.events.emit("sidebar:resize");
|
|
31691
33040
|
}
|
|
31692
33041
|
|
|
31693
33042
|
function showSidebar(id, skipShowSidebar) {
|
|
31694
33043
|
if (id === ":first") {
|
|
31695
|
-
|
|
33044
|
+
// Show the last selected tab for each sidebar
|
|
33045
|
+
Object.keys(sidebars).forEach(function(sidebarKey) {
|
|
33046
|
+
const sidebar = sidebars[sidebarKey];
|
|
33047
|
+
let lastTabId = lastSessionSelectedTabs[sidebarKey];
|
|
33048
|
+
if (!lastTabId) {
|
|
33049
|
+
lastTabId = sidebar.tabBar.children('button').first().attr('data-tab-id');
|
|
33050
|
+
}
|
|
33051
|
+
showSidebar(lastTabId, true)
|
|
33052
|
+
})
|
|
33053
|
+
return
|
|
31696
33054
|
}
|
|
31697
33055
|
if (id) {
|
|
31698
|
-
|
|
31699
|
-
|
|
31700
|
-
|
|
31701
|
-
|
|
31702
|
-
|
|
31703
|
-
|
|
33056
|
+
const tabOptions = knownTabs[id];
|
|
33057
|
+
if (tabOptions) {
|
|
33058
|
+
const targetSidebar = tabOptions.target === 'secondary' ? sidebars.secondary : sidebars.primary;
|
|
33059
|
+
targetSidebar.content.children().hide();
|
|
33060
|
+
targetSidebar.footer.children().hide();
|
|
33061
|
+
if (tabOptions.onchange) {
|
|
33062
|
+
tabOptions.onchange.call(tabOptions);
|
|
33063
|
+
}
|
|
33064
|
+
$(tabOptions.wrapper).show();
|
|
33065
|
+
if (tabOptions.toolbar) {
|
|
33066
|
+
$(tabOptions.toolbar).show();
|
|
33067
|
+
}
|
|
33068
|
+
RED.settings.setLocal("last-sidebar-tab-" + targetSidebar.id, tabOptions.id)
|
|
33069
|
+
targetSidebar.tabBar.find('button').removeClass('selected')
|
|
33070
|
+
targetSidebar.tabBar.find('button[data-tab-id="'+id+'"]').addClass('selected')
|
|
33071
|
+
targetSidebar.activeTab = id
|
|
33072
|
+
|
|
33073
|
+
if (!skipShowSidebar && !RED.menu.isSelected(targetSidebar.menuToggle)) {
|
|
33074
|
+
RED.menu.setSelected(targetSidebar.menuToggle,true);
|
|
33075
|
+
}
|
|
31704
33076
|
}
|
|
31705
33077
|
}
|
|
31706
33078
|
}
|
|
31707
33079
|
|
|
31708
33080
|
function containsTab(id) {
|
|
31709
|
-
return
|
|
33081
|
+
return sidebars.primary.tabs.contains(id);
|
|
31710
33082
|
}
|
|
31711
33083
|
|
|
31712
|
-
function
|
|
31713
|
-
|
|
31714
|
-
|
|
31715
|
-
|
|
31716
|
-
|
|
31717
|
-
|
|
31718
|
-
|
|
31719
|
-
|
|
31720
|
-
tab.onchange.call(tab);
|
|
31721
|
-
}
|
|
31722
|
-
$(tab.wrapper).show();
|
|
31723
|
-
if (tab.toolbar) {
|
|
31724
|
-
$(tab.toolbar).show();
|
|
31725
|
-
}
|
|
31726
|
-
RED.settings.setLocal("last-sidebar-tab", tab.id)
|
|
31727
|
-
},
|
|
31728
|
-
onremove: function(tab) {
|
|
31729
|
-
$(tab.wrapper).hide();
|
|
31730
|
-
if (tab.onremove) {
|
|
31731
|
-
tab.onremove.call(tab);
|
|
31732
|
-
}
|
|
31733
|
-
},
|
|
31734
|
-
// minimumActiveTabWidth: 70,
|
|
31735
|
-
collapsible: true,
|
|
31736
|
-
onreorder: function(order) {
|
|
31737
|
-
RED.settings.set("editor.sidebar.order",order);
|
|
31738
|
-
},
|
|
31739
|
-
order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])
|
|
31740
|
-
// scrollable: true
|
|
31741
|
-
});
|
|
33084
|
+
function setupSidebar(sidebar) {
|
|
33085
|
+
sidebar.container.addClass("red-ui-sidebar").addClass('red-ui-sidebar-' + sidebar.direction);
|
|
33086
|
+
sidebar.container.width(sidebar.defaultWidth);
|
|
33087
|
+
sidebar.separator = setupSidebarSeparator(sidebar);
|
|
33088
|
+
sidebar.tabBar = setupSidebarTabs(sidebar)
|
|
33089
|
+
sidebar.content = $('<div class="red-ui-sidebar-content"></div>').appendTo(sidebar.container);
|
|
33090
|
+
sidebar.footer = $('<div class="red-ui-sidebar-footer"></div>').appendTo(sidebar.container);
|
|
33091
|
+
sidebar.shade = $('<div class="red-ui-sidebar-shade hide"></div>').appendTo(sidebar.container);
|
|
31742
33092
|
|
|
31743
|
-
|
|
31744
|
-
|
|
31745
|
-
|
|
33093
|
+
}
|
|
33094
|
+
function init () {
|
|
33095
|
+
sidebars.primary.container = $("#red-ui-sidebar");
|
|
33096
|
+
setupSidebar(sidebars.primary)
|
|
33097
|
+
sidebars.secondary.container = $("#red-ui-sidebar-left");
|
|
33098
|
+
setupSidebar(sidebars.secondary)
|
|
31746
33099
|
|
|
31747
33100
|
RED.actions.add("core:toggle-sidebar",function(state){
|
|
31748
33101
|
if (state === undefined) {
|
|
31749
|
-
RED.menu.toggleSelected(
|
|
33102
|
+
RED.menu.toggleSelected(sidebars.primary.menuToggle);
|
|
31750
33103
|
} else {
|
|
31751
|
-
toggleSidebar(state);
|
|
33104
|
+
toggleSidebar(sidebars.primary, state);
|
|
33105
|
+
}
|
|
33106
|
+
});
|
|
33107
|
+
RED.actions.add("core:toggle-palette", function(state) {
|
|
33108
|
+
if (state === undefined) {
|
|
33109
|
+
RED.menu.toggleSelected(sidebars.secondary.menuToggle);
|
|
33110
|
+
} else {
|
|
33111
|
+
toggleSidebar(sidebars.secondary, state);
|
|
31752
33112
|
}
|
|
31753
33113
|
});
|
|
31754
|
-
RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar");
|
|
31755
|
-
|
|
31756
|
-
lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab")
|
|
31757
33114
|
|
|
31758
|
-
|
|
31759
|
-
|
|
31760
|
-
|
|
31761
|
-
|
|
31762
|
-
|
|
31763
|
-
if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); }
|
|
33115
|
+
// Remember the last selected tab for each sidebar before
|
|
33116
|
+
// the tabs are readded causing the state to get updated
|
|
33117
|
+
Object.keys(sidebars).forEach(function(sidebarKey) {
|
|
33118
|
+
lastSessionSelectedTabs[sidebarKey] = RED.settings.getLocal("last-sidebar-tab-" + sidebarKey)
|
|
33119
|
+
})
|
|
31764
33120
|
}
|
|
31765
33121
|
|
|
31766
33122
|
return {
|
|
@@ -31808,7 +33164,6 @@ RED.palette = (function() {
|
|
|
31808
33164
|
];
|
|
31809
33165
|
|
|
31810
33166
|
var categoryContainers = {};
|
|
31811
|
-
var sidebarControls;
|
|
31812
33167
|
|
|
31813
33168
|
let paletteState = { filter: "", collapsed: [] };
|
|
31814
33169
|
|
|
@@ -32085,6 +33440,7 @@ RED.palette = (function() {
|
|
|
32085
33440
|
width: "300px",
|
|
32086
33441
|
content: "hi",
|
|
32087
33442
|
delay: { show: 750, hide: 50 }
|
|
33443
|
+
// direction: "left"
|
|
32088
33444
|
});
|
|
32089
33445
|
|
|
32090
33446
|
d.data('popover',popover);
|
|
@@ -32107,7 +33463,8 @@ RED.palette = (function() {
|
|
|
32107
33463
|
revert: 'invalid',
|
|
32108
33464
|
revertDuration: 200,
|
|
32109
33465
|
containment:'#red-ui-main-container',
|
|
32110
|
-
start: function() {
|
|
33466
|
+
start: function(e, ui) {
|
|
33467
|
+
ui.helper.css('z-index', 1000);
|
|
32111
33468
|
dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
|
|
32112
33469
|
paletteWidth = $("#red-ui-palette").width();
|
|
32113
33470
|
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
|
|
@@ -32133,7 +33490,9 @@ RED.palette = (function() {
|
|
|
32133
33490
|
},
|
|
32134
33491
|
drag: function(e,ui) {
|
|
32135
33492
|
var paletteNode = getPaletteNode(nt);
|
|
32136
|
-
ui.originalPosition.left
|
|
33493
|
+
console.log(ui.originalPosition.left, paletteNode.offset().left)
|
|
33494
|
+
// ui.originalPosition.left = paletteNode.offset().left;
|
|
33495
|
+
// console.log(paletteNode.offset())
|
|
32137
33496
|
if (dropEnabled) {
|
|
32138
33497
|
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
|
|
32139
33498
|
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
|
|
@@ -32382,11 +33741,24 @@ RED.palette = (function() {
|
|
|
32382
33741
|
|
|
32383
33742
|
function init() {
|
|
32384
33743
|
|
|
33744
|
+
const content = $('<div id="red-ui-palette" class="red-ui-sidebar-tab-content">')
|
|
33745
|
+
const toolbar = $('<div></div>');
|
|
33746
|
+
RED.sidebar.addTab({
|
|
33747
|
+
target: 'secondary',
|
|
33748
|
+
id: "palette",
|
|
33749
|
+
label: "Palette",
|
|
33750
|
+
name: "Palette",
|
|
33751
|
+
icon: "red/images/subflow_tab.svg",
|
|
33752
|
+
content,
|
|
33753
|
+
toolbar,
|
|
33754
|
+
pinned: true,
|
|
33755
|
+
enableOnEdit: true
|
|
33756
|
+
});
|
|
33757
|
+
|
|
32385
33758
|
$('<img src="red/images/spin.svg" class="red-ui-palette-spinner hide"/>').appendTo("#red-ui-palette");
|
|
32386
33759
|
$('<div id="red-ui-palette-search" class="red-ui-palette-search hide"><input type="text" data-i18n="[placeholder]palette.filter"></input></div>').appendTo("#red-ui-palette");
|
|
32387
33760
|
$('<div id="red-ui-palette-container" class="red-ui-palette-scroll hide"></div>').appendTo("#red-ui-palette");
|
|
32388
|
-
$('<div
|
|
32389
|
-
$('<div id="red-ui-palette-shade" class="hide"></div>').appendTo("#red-ui-palette");
|
|
33761
|
+
// $('<div id="red-ui-palette-shade" class="hide"></div>').appendTo("#red-ui-palette");
|
|
32390
33762
|
|
|
32391
33763
|
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
|
32392
33764
|
|
|
@@ -32445,19 +33817,6 @@ RED.palette = (function() {
|
|
|
32445
33817
|
}
|
|
32446
33818
|
});
|
|
32447
33819
|
|
|
32448
|
-
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
|
32449
|
-
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
|
32450
|
-
|
|
32451
|
-
sidebarControls.on("click", function() {
|
|
32452
|
-
RED.menu.toggleSelected("menu-item-palette");
|
|
32453
|
-
})
|
|
32454
|
-
$("#red-ui-palette").on("mouseenter", function() {
|
|
32455
|
-
sidebarControls.toggle("slide", { direction: "left" }, 200);
|
|
32456
|
-
})
|
|
32457
|
-
$("#red-ui-palette").on("mouseleave", function() {
|
|
32458
|
-
sidebarControls.stop(false,true);
|
|
32459
|
-
sidebarControls.hide();
|
|
32460
|
-
})
|
|
32461
33820
|
var userCategories = [];
|
|
32462
33821
|
if (RED.settings.paletteCategories) {
|
|
32463
33822
|
userCategories = RED.settings.paletteCategories;
|
|
@@ -32479,7 +33838,7 @@ RED.palette = (function() {
|
|
|
32479
33838
|
}
|
|
32480
33839
|
});
|
|
32481
33840
|
|
|
32482
|
-
var paletteFooterButtons = $('<span class="button-group"></span>').appendTo(
|
|
33841
|
+
var paletteFooterButtons = $('<span class="button-group"></span>').appendTo(toolbar);
|
|
32483
33842
|
var paletteCollapseAll = $('<button type="button" class="red-ui-footer-button"><i class="fa fa-angle-double-up"></i></button>').appendTo(paletteFooterButtons);
|
|
32484
33843
|
paletteCollapseAll.on("click", function(e) {
|
|
32485
33844
|
e.preventDefault();
|
|
@@ -32502,13 +33861,7 @@ RED.palette = (function() {
|
|
|
32502
33861
|
});
|
|
32503
33862
|
RED.popover.tooltip(paletteExpandAll,RED._('palette.actions.expand-all'));
|
|
32504
33863
|
|
|
32505
|
-
|
|
32506
|
-
if (state === undefined) {
|
|
32507
|
-
RED.menu.toggleSelected("menu-item-palette");
|
|
32508
|
-
} else {
|
|
32509
|
-
togglePalette(state);
|
|
32510
|
-
}
|
|
32511
|
-
});
|
|
33864
|
+
|
|
32512
33865
|
|
|
32513
33866
|
try {
|
|
32514
33867
|
paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
|
|
@@ -32526,18 +33879,6 @@ RED.palette = (function() {
|
|
|
32526
33879
|
}, 10000)
|
|
32527
33880
|
}
|
|
32528
33881
|
|
|
32529
|
-
function togglePalette(state) {
|
|
32530
|
-
if (!state) {
|
|
32531
|
-
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
|
32532
|
-
sidebarControls.hide();
|
|
32533
|
-
sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left");
|
|
32534
|
-
} else {
|
|
32535
|
-
$("#red-ui-main-container").removeClass("red-ui-palette-closed");
|
|
32536
|
-
sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left");
|
|
32537
|
-
}
|
|
32538
|
-
setTimeout(function() { $(window).trigger("resize"); } ,200);
|
|
32539
|
-
}
|
|
32540
|
-
|
|
32541
33882
|
function getCategories() {
|
|
32542
33883
|
var categories = [];
|
|
32543
33884
|
$("#red-ui-palette-container .red-ui-palette-category").each(function(i,d) {
|
|
@@ -32685,9 +34026,10 @@ RED.sidebar.info = (function() {
|
|
|
32685
34026
|
|
|
32686
34027
|
RED.sidebar.addTab({
|
|
32687
34028
|
id: "info",
|
|
34029
|
+
// target: "secondary",
|
|
32688
34030
|
label: RED._("sidebar.info.label"),
|
|
32689
34031
|
name: RED._("sidebar.info.name"),
|
|
32690
|
-
|
|
34032
|
+
icon: "red/images/explorer.svg",
|
|
32691
34033
|
action:"core:show-info-tab",
|
|
32692
34034
|
content: content,
|
|
32693
34035
|
pinned: true,
|
|
@@ -32723,6 +34065,8 @@ RED.sidebar.info = (function() {
|
|
|
32723
34065
|
tips.stop();
|
|
32724
34066
|
}
|
|
32725
34067
|
|
|
34068
|
+
resizeStack();
|
|
34069
|
+
|
|
32726
34070
|
}
|
|
32727
34071
|
|
|
32728
34072
|
function show() {
|
|
@@ -40019,6 +41363,9 @@ RED.editor = (function() {
|
|
|
40019
41363
|
changes.inputLabels = node.inputLabels;
|
|
40020
41364
|
node.inputLabels = newValue;
|
|
40021
41365
|
changed = true;
|
|
41366
|
+
if (node.type === "subflow") {
|
|
41367
|
+
node.in[0].dirty = true
|
|
41368
|
+
}
|
|
40022
41369
|
}
|
|
40023
41370
|
hasNonBlankLabel = false;
|
|
40024
41371
|
newValue = new Array(node.outputs);
|
|
@@ -45668,6 +47015,8 @@ RED.eventLog = (function() {
|
|
|
45668
47015
|
}
|
|
45669
47016
|
|
|
45670
47017
|
function handleWindowResize() {
|
|
47018
|
+
let sidebarWidth = $("#red-ui-sidebar").is(":visible") ? $("#red-ui-sidebar").outerWidth() + $("#red-ui-sidebar-separator").outerWidth() : 0;
|
|
47019
|
+
$("#red-ui-editor-stack").css('right', sidebarWidth + $("#red-ui-sidebar-tab-bar").outerWidth() + 1);
|
|
45671
47020
|
if (stack.length > 0) {
|
|
45672
47021
|
var tray = stack[stack.length-1];
|
|
45673
47022
|
if (tray.options.maximized || tray.width > $("#red-ui-editor-stack").position().left-8) {
|
|
@@ -48829,6 +50178,12 @@ RED.search = (function() {
|
|
|
48829
50178
|
$('<div>',{class:"red-ui-search-result-node-type"}).text(node.type).appendTo(contentDiv);
|
|
48830
50179
|
$('<div>',{class:"red-ui-search-result-node-id"}).text(node.id).appendTo(contentDiv);
|
|
48831
50180
|
|
|
50181
|
+
div.on("mouseover", function(evt) {
|
|
50182
|
+
if ( node.z == RED.workspaces.active() ) {
|
|
50183
|
+
RED.view.reveal(node.id)
|
|
50184
|
+
}
|
|
50185
|
+
});
|
|
50186
|
+
|
|
48832
50187
|
div.on("click", function(evt) {
|
|
48833
50188
|
evt.preventDefault();
|
|
48834
50189
|
currentIndex = i;
|
|
@@ -48907,8 +50262,7 @@ RED.search = (function() {
|
|
|
48907
50262
|
$("#red-ui-header-shade").show();
|
|
48908
50263
|
$("#red-ui-editor-shade").show();
|
|
48909
50264
|
$("#red-ui-palette-shade").show();
|
|
48910
|
-
$("
|
|
48911
|
-
$("#red-ui-sidebar-separator").hide();
|
|
50265
|
+
$(".red-ui-sidebar-shade").show();
|
|
48912
50266
|
|
|
48913
50267
|
if (dialog === null) {
|
|
48914
50268
|
createDialog();
|
|
@@ -48932,8 +50286,7 @@ RED.search = (function() {
|
|
|
48932
50286
|
$("#red-ui-header-shade").hide();
|
|
48933
50287
|
$("#red-ui-editor-shade").hide();
|
|
48934
50288
|
$("#red-ui-palette-shade").hide();
|
|
48935
|
-
$("
|
|
48936
|
-
$("#red-ui-sidebar-separator").show();
|
|
50289
|
+
$(".red-ui-sidebar-shade").hide();
|
|
48937
50290
|
if (dialog !== null) {
|
|
48938
50291
|
dialog.slideUp(200,function() {
|
|
48939
50292
|
searchInput.searchBox('value','');
|
|
@@ -49033,7 +50386,7 @@ RED.search = (function() {
|
|
|
49033
50386
|
$("#red-ui-header-shade").on('mousedown',hide);
|
|
49034
50387
|
$("#red-ui-editor-shade").on('mousedown',hide);
|
|
49035
50388
|
$("#red-ui-palette-shade").on('mousedown',hide);
|
|
49036
|
-
$("
|
|
50389
|
+
$(".red-ui-sidebar-shade").on('mousedown',hide);
|
|
49037
50390
|
|
|
49038
50391
|
$("#red-ui-view-searchtools-close").on("click", function close() {
|
|
49039
50392
|
clearActiveSearch();
|
|
@@ -49295,6 +50648,11 @@ RED.search = (function() {
|
|
|
49295
50648
|
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }
|
|
49296
50649
|
)
|
|
49297
50650
|
}
|
|
50651
|
+
if (hasSelection && canEdit) {
|
|
50652
|
+
menuItems.push(
|
|
50653
|
+
{ onselect: 'core:convert-to-subflow', label: RED._("menu.label.selectionToSubflow") }
|
|
50654
|
+
)
|
|
50655
|
+
}
|
|
49298
50656
|
menuItems.push(
|
|
49299
50657
|
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }
|
|
49300
50658
|
)
|
|
@@ -49519,8 +50877,7 @@ RED.actionList = (function() {
|
|
|
49519
50877
|
$("#red-ui-header-shade").show();
|
|
49520
50878
|
$("#red-ui-editor-shade").show();
|
|
49521
50879
|
$("#red-ui-palette-shade").show();
|
|
49522
|
-
$("
|
|
49523
|
-
$("#red-ui-sidebar-separator").hide();
|
|
50880
|
+
$(".red-ui-sidebar-shade").show();
|
|
49524
50881
|
if (dialog === null) {
|
|
49525
50882
|
createDialog();
|
|
49526
50883
|
}
|
|
@@ -49554,8 +50911,7 @@ RED.actionList = (function() {
|
|
|
49554
50911
|
$("#red-ui-header-shade").hide();
|
|
49555
50912
|
$("#red-ui-editor-shade").hide();
|
|
49556
50913
|
$("#red-ui-palette-shade").hide();
|
|
49557
|
-
$("
|
|
49558
|
-
$("#red-ui-sidebar-separator").show();
|
|
50914
|
+
$(".red-ui-sidebar-shade").hide();
|
|
49559
50915
|
if (dialog !== null) {
|
|
49560
50916
|
dialog.slideUp(200,function() {
|
|
49561
50917
|
searchInput.searchBox('value','');
|
|
@@ -49587,7 +50943,7 @@ RED.actionList = (function() {
|
|
|
49587
50943
|
$("#red-ui-header-shade").on('mousedown',hide);
|
|
49588
50944
|
$("#red-ui-editor-shade").on('mousedown',hide);
|
|
49589
50945
|
$("#red-ui-palette-shade").on('mousedown',hide);
|
|
49590
|
-
$("
|
|
50946
|
+
$(".red-ui-sidebar-shade").on('mousedown',hide);
|
|
49591
50947
|
}
|
|
49592
50948
|
|
|
49593
50949
|
return {
|
|
@@ -52409,7 +53765,7 @@ RED.userSettings = (function() {
|
|
|
52409
53765
|
});
|
|
52410
53766
|
settingsContent.i18n();
|
|
52411
53767
|
settingsTabs.activateTab("red-ui-settings-tab-"+(initialTab||'view'))
|
|
52412
|
-
$("
|
|
53768
|
+
$(".red-ui-sidebar-shade").show();
|
|
52413
53769
|
},
|
|
52414
53770
|
close: function() {
|
|
52415
53771
|
settingsVisible = false;
|
|
@@ -52418,7 +53774,7 @@ RED.userSettings = (function() {
|
|
|
52418
53774
|
pane.close();
|
|
52419
53775
|
}
|
|
52420
53776
|
});
|
|
52421
|
-
$("
|
|
53777
|
+
$(".red-ui-sidebar-shade").hide();
|
|
52422
53778
|
|
|
52423
53779
|
},
|
|
52424
53780
|
show: function() {}
|
|
@@ -55211,7 +56567,7 @@ RED.projects.settings = (function() {
|
|
|
55211
56567
|
});
|
|
55212
56568
|
settingsContent.i18n();
|
|
55213
56569
|
settingsTabs.activateTab("red-ui-project-settings-tab-"+(initialTab||'main'))
|
|
55214
|
-
$("
|
|
56570
|
+
$(".red-ui-sidebar-shade").show();
|
|
55215
56571
|
},
|
|
55216
56572
|
close: function() {
|
|
55217
56573
|
settingsVisible = false;
|
|
@@ -55220,7 +56576,7 @@ RED.projects.settings = (function() {
|
|
|
55220
56576
|
pane.close();
|
|
55221
56577
|
}
|
|
55222
56578
|
});
|
|
55223
|
-
$("
|
|
56579
|
+
$(".red-ui-sidebar-shade").hide();
|
|
55224
56580
|
|
|
55225
56581
|
},
|
|
55226
56582
|
show: function() {}
|
|
@@ -59234,10 +60590,15 @@ RED.touch.radialMenu = (function() {
|
|
|
59234
60590
|
|
|
59235
60591
|
function listTour() {
|
|
59236
60592
|
return [
|
|
60593
|
+
{
|
|
60594
|
+
id: "5_0",
|
|
60595
|
+
label: "5.0",
|
|
60596
|
+
path: "./tours/welcome.js"
|
|
60597
|
+
},
|
|
59237
60598
|
{
|
|
59238
60599
|
id: "4_1",
|
|
59239
60600
|
label: "4.1",
|
|
59240
|
-
path: "./tours/welcome.js"
|
|
60601
|
+
path: "./tours/4.1/welcome.js"
|
|
59241
60602
|
},
|
|
59242
60603
|
{
|
|
59243
60604
|
id: "4_0",
|