@node-red/editor-client 4.1.2 → 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 +18 -0
- package/public/red/images/explorer.svg +1 -0
- package/public/red/keymap.json +2 -1
- package/public/red/red.js +1778 -431
- 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();
|
|
928
|
+
|
|
929
|
+
$("#red-ui-main-container").show();
|
|
930
|
+
RED.events.emit("sidebar:resize")
|
|
922
931
|
|
|
923
|
-
|
|
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);
|
|
@@ -13203,7 +13218,7 @@ RED.menu = (function() {
|
|
|
13203
13218
|
} else {
|
|
13204
13219
|
for (var i=0;i<groupItems.length;i++) {
|
|
13205
13220
|
var groupItem = groupItems[i];
|
|
13206
|
-
var label = $(groupItem).find(".red-ui-menu-label").
|
|
13221
|
+
var label = $(groupItem).find(".red-ui-menu-label span").text();
|
|
13207
13222
|
if (opt.label < label) {
|
|
13208
13223
|
$(groupItem).before(item);
|
|
13209
13224
|
break;
|
|
@@ -14869,7 +14884,6 @@ RED.tabs = (function() {
|
|
|
14869
14884
|
ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""})
|
|
14870
14885
|
}
|
|
14871
14886
|
}
|
|
14872
|
-
|
|
14873
14887
|
}
|
|
14874
14888
|
|
|
14875
14889
|
ul.find("li.red-ui-tab a")
|
|
@@ -15369,7 +15383,8 @@ RED.tabs = (function() {
|
|
|
15369
15383
|
pinnedButtons["__menu__"].appendTo(collapsedButtonsRow);
|
|
15370
15384
|
updateTabWidths();
|
|
15371
15385
|
}
|
|
15372
|
-
}
|
|
15386
|
+
},
|
|
15387
|
+
container: wrapper
|
|
15373
15388
|
}
|
|
15374
15389
|
return tabAPI;
|
|
15375
15390
|
}
|
|
@@ -17932,13 +17947,13 @@ RED.deploy = (function() {
|
|
|
17932
17947
|
$("#red-ui-header-shade").show();
|
|
17933
17948
|
$("#red-ui-editor-shade").show();
|
|
17934
17949
|
$("#red-ui-palette-shade").show();
|
|
17935
|
-
$("
|
|
17950
|
+
$(".red-ui-sidebar-shade").show();
|
|
17936
17951
|
}
|
|
17937
17952
|
function shadeHide() {
|
|
17938
17953
|
$("#red-ui-header-shade").hide();
|
|
17939
17954
|
$("#red-ui-editor-shade").hide();
|
|
17940
17955
|
$("#red-ui-palette-shade").hide();
|
|
17941
|
-
$("
|
|
17956
|
+
$(".red-ui-sidebar-shade").hide();
|
|
17942
17957
|
}
|
|
17943
17958
|
function deployButtonSetBusy(){
|
|
17944
17959
|
$(".red-ui-deploy-button-content").css('opacity',0);
|
|
@@ -19752,11 +19767,11 @@ RED.diagnostics = (function () {
|
|
|
19752
19767
|
diffTable.finish();
|
|
19753
19768
|
diffTable.list.show();
|
|
19754
19769
|
},300);
|
|
19755
|
-
$("
|
|
19770
|
+
$(".red-ui-sidebar-shade").show();
|
|
19756
19771
|
},
|
|
19757
19772
|
close: function() {
|
|
19758
19773
|
diffVisible = false;
|
|
19759
|
-
$("
|
|
19774
|
+
$(".red-ui-sidebar-shade").hide();
|
|
19760
19775
|
|
|
19761
19776
|
},
|
|
19762
19777
|
show: function() {
|
|
@@ -22683,11 +22698,29 @@ RED.view = (function() {
|
|
|
22683
22698
|
node_height = 30,
|
|
22684
22699
|
dblClickInterval = 650;
|
|
22685
22700
|
|
|
22701
|
+
var cancelInProgressAnimation = null; // For smooth zoom animation
|
|
22702
|
+
|
|
22686
22703
|
var touchLongPressTimeout = 1000,
|
|
22687
22704
|
startTouchDistance = 0,
|
|
22688
22705
|
startTouchCenter = [],
|
|
22689
22706
|
moveTouchCenter = [],
|
|
22690
|
-
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;
|
|
22691
22724
|
|
|
22692
22725
|
var workspaceScrollPositions = {};
|
|
22693
22726
|
|
|
@@ -22964,6 +22997,24 @@ RED.view = (function() {
|
|
|
22964
22997
|
function init() {
|
|
22965
22998
|
|
|
22966
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
|
+
|
|
22967
23018
|
chart.on('contextmenu', function(evt) {
|
|
22968
23019
|
if (RED.view.DEBUG) {
|
|
22969
23020
|
console.warn("contextmenu", { mouse_mode, event: d3.event });
|
|
@@ -23008,8 +23059,9 @@ RED.view = (function() {
|
|
|
23008
23059
|
lasso.remove();
|
|
23009
23060
|
lasso = null;
|
|
23010
23061
|
}
|
|
23011
|
-
} else if (mouse_mode === RED.state.PANNING
|
|
23012
|
-
|
|
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');
|
|
23013
23065
|
} else if (slicePath) {
|
|
23014
23066
|
if (d3.event.buttons !== 2) {
|
|
23015
23067
|
slicePath.remove();
|
|
@@ -23026,11 +23078,15 @@ RED.view = (function() {
|
|
|
23026
23078
|
if (RED.touch.radialMenu.active()) {
|
|
23027
23079
|
return;
|
|
23028
23080
|
}
|
|
23081
|
+
// End gesture when touches end
|
|
23082
|
+
RED.view.zoomAnimator.endGesture();
|
|
23029
23083
|
canvasMouseUp.call(this);
|
|
23030
23084
|
})
|
|
23031
23085
|
.on("touchcancel", function() {
|
|
23032
23086
|
if (RED.view.DEBUG) { console.warn("eventLayer.touchcancel", mouse_mode); }
|
|
23033
23087
|
d3.event.preventDefault();
|
|
23088
|
+
// End gesture when touches are cancelled
|
|
23089
|
+
RED.view.zoomAnimator.endGesture();
|
|
23034
23090
|
canvasMouseUp.call(this);
|
|
23035
23091
|
})
|
|
23036
23092
|
.on("touchstart", function() {
|
|
@@ -23056,6 +23112,20 @@ RED.view = (function() {
|
|
|
23056
23112
|
touch1["pageY"]+(a/2)
|
|
23057
23113
|
]
|
|
23058
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);
|
|
23059
23129
|
} else {
|
|
23060
23130
|
var obj = d3.select(document.body);
|
|
23061
23131
|
touch0 = d3.event.touches.item(0);
|
|
@@ -23105,33 +23175,93 @@ RED.view = (function() {
|
|
|
23105
23175
|
var offset = chart.offset();
|
|
23106
23176
|
var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
|
|
23107
23177
|
var moveTouchDistance = Math.sqrt((a*a)+(b*b));
|
|
23108
|
-
|
|
23109
|
-
|
|
23110
|
-
|
|
23178
|
+
|
|
23179
|
+
// Calculate center point of two fingers
|
|
23180
|
+
var currentTouchCenter = [
|
|
23181
|
+
(touch0["pageX"] + touch1["pageX"]) / 2,
|
|
23182
|
+
(touch0["pageY"] + touch1["pageY"]) / 2
|
|
23111
23183
|
];
|
|
23112
23184
|
|
|
23113
23185
|
if (!isNaN(moveTouchDistance)) {
|
|
23114
|
-
|
|
23115
|
-
|
|
23116
|
-
|
|
23117
|
-
|
|
23118
|
-
|
|
23119
|
-
|
|
23120
|
-
|
|
23121
|
-
|
|
23122
|
-
|
|
23123
|
-
|
|
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
|
+
}
|
|
23124
23256
|
|
|
23125
|
-
|
|
23126
|
-
chart.scrollTop(scrollPos[1]+deltaTouchCenter[1]);
|
|
23127
|
-
redraw();
|
|
23257
|
+
// Don't update startTouchDistance - keep initial distance for ratio calculation
|
|
23128
23258
|
}
|
|
23129
23259
|
}
|
|
23130
23260
|
d3.event.preventDefault();
|
|
23131
23261
|
});
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23262
|
+
|
|
23263
|
+
const handleChartKeyboardEvents = (event) => {
|
|
23264
|
+
// Handle Alt toggle for pulling nodes out of groups
|
|
23135
23265
|
if (mouse_mode === RED.state.MOVING_ACTIVE && event.key === 'Alt' && groupAddParentGroup) {
|
|
23136
23266
|
RED.nodes.group(groupAddParentGroup).dirty = true
|
|
23137
23267
|
for (let n = 0; n<movingSet.length(); n++) {
|
|
@@ -23150,10 +23280,67 @@ RED.view = (function() {
|
|
|
23150
23280
|
}
|
|
23151
23281
|
}
|
|
23152
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
|
+
}
|
|
23153
23305
|
}
|
|
23154
23306
|
}
|
|
23155
|
-
|
|
23156
|
-
|
|
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
|
+
})
|
|
23157
23344
|
|
|
23158
23345
|
// Workspace Background
|
|
23159
23346
|
eventLayer.append("svg:rect")
|
|
@@ -23245,22 +23432,194 @@ RED.view = (function() {
|
|
|
23245
23432
|
'<button class="red-ui-footer-button" id="red-ui-view-zoom-out"><i class="fa fa-minus"></i></button>'+
|
|
23246
23433
|
'<button class="red-ui-footer-button" id="red-ui-view-zoom-zero"><i class="fa fa-circle-o"></i></button>'+
|
|
23247
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>'+
|
|
23248
23436
|
'</span>')
|
|
23249
23437
|
})
|
|
23250
23438
|
|
|
23251
|
-
$("#red-ui-view-zoom-out").on("click", zoomOut);
|
|
23439
|
+
$("#red-ui-view-zoom-out").on("click", function() { zoomOut(); });
|
|
23252
23440
|
RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out');
|
|
23253
23441
|
$("#red-ui-view-zoom-zero").on("click", zoomZero);
|
|
23254
23442
|
RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset');
|
|
23255
|
-
$("#red-ui-view-zoom-in").on("click", zoomIn);
|
|
23443
|
+
$("#red-ui-view-zoom-in").on("click", function() { zoomIn(); });
|
|
23256
23444
|
RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in');
|
|
23257
|
-
|
|
23258
|
-
|
|
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
|
|
23259
23596
|
evt.preventDefault();
|
|
23260
23597
|
evt.stopPropagation();
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
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);
|
|
23264
23623
|
}
|
|
23265
23624
|
});
|
|
23266
23625
|
|
|
@@ -23431,6 +23790,12 @@ RED.view = (function() {
|
|
|
23431
23790
|
});
|
|
23432
23791
|
chart.on("blur", function() {
|
|
23433
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
|
+
}
|
|
23434
23799
|
});
|
|
23435
23800
|
|
|
23436
23801
|
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
|
|
@@ -23495,6 +23860,7 @@ RED.view = (function() {
|
|
|
23495
23860
|
RED.actions.add("core:zoom-in",zoomIn);
|
|
23496
23861
|
RED.actions.add("core:zoom-out",zoomOut);
|
|
23497
23862
|
RED.actions.add("core:zoom-reset",zoomZero);
|
|
23863
|
+
RED.actions.add("core:zoom-fit",zoomToFitAll);
|
|
23498
23864
|
RED.actions.add("core:enable-selected-nodes", function() { setSelectedNodeState(false)});
|
|
23499
23865
|
RED.actions.add("core:disable-selected-nodes", function() { setSelectedNodeState(true)});
|
|
23500
23866
|
|
|
@@ -23610,6 +23976,9 @@ RED.view = (function() {
|
|
|
23610
23976
|
RED.settings.setLocal('scroll-positions', JSON.stringify(workspaceScrollPositions) )
|
|
23611
23977
|
}
|
|
23612
23978
|
chart.on("scroll", function() {
|
|
23979
|
+
// Track scroll velocity for momentum
|
|
23980
|
+
handleScroll();
|
|
23981
|
+
|
|
23613
23982
|
if (RED.settings.get("editor.view.view-store-position")) {
|
|
23614
23983
|
if (onScrollTimer) {
|
|
23615
23984
|
clearTimeout(onScrollTimer)
|
|
@@ -23887,12 +24256,26 @@ RED.view = (function() {
|
|
|
23887
24256
|
return;
|
|
23888
24257
|
}
|
|
23889
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
|
+
|
|
23890
24271
|
if (d3.event.button === 1) {
|
|
23891
24272
|
// Middle Click pan
|
|
23892
24273
|
d3.event.preventDefault();
|
|
23893
24274
|
mouse_mode = RED.state.PANNING;
|
|
23894
24275
|
mouse_position = [d3.event.pageX,d3.event.pageY]
|
|
23895
24276
|
scroll_position = [chart.scrollLeft(),chart.scrollTop()];
|
|
24277
|
+
// Change cursor to grabbing while actively panning
|
|
24278
|
+
outer.style('cursor', 'grabbing');
|
|
23896
24279
|
return;
|
|
23897
24280
|
}
|
|
23898
24281
|
if (d3.event.button === 2) {
|
|
@@ -24424,6 +24807,30 @@ RED.view = (function() {
|
|
|
24424
24807
|
redraw();
|
|
24425
24808
|
}
|
|
24426
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
|
+
|
|
24427
24834
|
function canvasMouseMove() {
|
|
24428
24835
|
var i;
|
|
24429
24836
|
var node;
|
|
@@ -24438,18 +24845,8 @@ RED.view = (function() {
|
|
|
24438
24845
|
//console.log(d3.mouse(this),container.offsetWidth,container.offsetHeight,container.scrollLeft,container.scrollTop);
|
|
24439
24846
|
|
|
24440
24847
|
if (mouse_mode === RED.state.PANNING) {
|
|
24441
|
-
|
|
24442
|
-
|
|
24443
|
-
var touch0 = d3.event.touches.item(0);
|
|
24444
|
-
pos = [touch0.pageX, touch0.pageY];
|
|
24445
|
-
}
|
|
24446
|
-
var deltaPos = [
|
|
24447
|
-
mouse_position[0]-pos[0],
|
|
24448
|
-
mouse_position[1]-pos[1]
|
|
24449
|
-
];
|
|
24450
|
-
|
|
24451
|
-
chart.scrollLeft(scroll_position[0]+deltaPos[0])
|
|
24452
|
-
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
|
|
24453
24850
|
return
|
|
24454
24851
|
}
|
|
24455
24852
|
|
|
@@ -24793,6 +25190,12 @@ RED.view = (function() {
|
|
|
24793
25190
|
}
|
|
24794
25191
|
if (mouse_mode === RED.state.PANNING) {
|
|
24795
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
|
+
}
|
|
24796
25199
|
return
|
|
24797
25200
|
}
|
|
24798
25201
|
if (mouse_mode === RED.state.SELECTING_NODE) {
|
|
@@ -25124,39 +25527,454 @@ RED.view = (function() {
|
|
|
25124
25527
|
|
|
25125
25528
|
}
|
|
25126
25529
|
|
|
25127
|
-
function
|
|
25128
|
-
|
|
25129
|
-
|
|
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);
|
|
25130
25616
|
}
|
|
25131
25617
|
}
|
|
25132
|
-
function
|
|
25133
|
-
|
|
25134
|
-
|
|
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]);
|
|
25135
25726
|
}
|
|
25136
25727
|
}
|
|
25137
|
-
|
|
25728
|
+
|
|
25138
25729
|
function searchFlows() { RED.actions.invoke("core:search", $(this).data("term")); }
|
|
25139
25730
|
function searchPrev() { RED.actions.invoke("core:search-previous"); }
|
|
25140
25731
|
function searchNext() { RED.actions.invoke("core:search-next"); }
|
|
25141
25732
|
|
|
25142
25733
|
|
|
25143
|
-
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)
|
|
25144
25746
|
var screenSize = [chart.width(),chart.height()];
|
|
25145
25747
|
var scrollPos = [chart.scrollLeft(),chart.scrollTop()];
|
|
25146
|
-
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
|
+
|
|
25147
25760
|
scaleFactor = factor;
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
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
|
+
}
|
|
25152
25774
|
|
|
25153
25775
|
RED.view.navigator.resize();
|
|
25154
25776
|
redraw();
|
|
25777
|
+
RED.events.emit("view:navigate");
|
|
25155
25778
|
if (RED.settings.get("editor.view.view-store-zoom")) {
|
|
25156
25779
|
RED.settings.setLocal('zoom-level', factor.toFixed(1))
|
|
25157
25780
|
}
|
|
25158
25781
|
}
|
|
25159
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
|
+
|
|
25160
25978
|
function selectNone() {
|
|
25161
25979
|
if (mouse_mode === RED.state.MOVING || mouse_mode === RED.state.MOVING_ACTIVE) {
|
|
25162
25980
|
return;
|
|
@@ -25861,6 +26679,9 @@ RED.view = (function() {
|
|
|
25861
26679
|
|
|
25862
26680
|
function portMouseDown(d,portType,portIndex, evt) {
|
|
25863
26681
|
if (RED.view.DEBUG) { console.warn("portMouseDown", mouse_mode,d,portType,portIndex); }
|
|
26682
|
+
if (spacebarPressed) {
|
|
26683
|
+
return
|
|
26684
|
+
}
|
|
25864
26685
|
clearSuggestedFlow();
|
|
25865
26686
|
RED.contextMenu.hide();
|
|
25866
26687
|
evt = evt || d3.event;
|
|
@@ -26290,6 +27111,9 @@ RED.view = (function() {
|
|
|
26290
27111
|
(d3.event || event).stopPropagation();
|
|
26291
27112
|
return;
|
|
26292
27113
|
}
|
|
27114
|
+
if (spacebarPressed) {
|
|
27115
|
+
return
|
|
27116
|
+
}
|
|
26293
27117
|
clearTimeout(portLabelHoverTimeout);
|
|
26294
27118
|
var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active
|
|
26295
27119
|
(
|
|
@@ -26446,6 +27270,9 @@ RED.view = (function() {
|
|
|
26446
27270
|
}
|
|
26447
27271
|
function nodeMouseDown(d) {
|
|
26448
27272
|
if (RED.view.DEBUG) { console.warn("nodeMouseDown", mouse_mode,d); }
|
|
27273
|
+
if (spacebarPressed) {
|
|
27274
|
+
return
|
|
27275
|
+
}
|
|
26449
27276
|
clearSuggestedFlow()
|
|
26450
27277
|
focusView();
|
|
26451
27278
|
RED.contextMenu.hide();
|
|
@@ -26624,6 +27451,9 @@ RED.view = (function() {
|
|
|
26624
27451
|
|
|
26625
27452
|
function nodeMouseOver(d) {
|
|
26626
27453
|
if (RED.view.DEBUG) { console.warn("nodeMouseOver", mouse_mode,d); }
|
|
27454
|
+
if (spacebarPressed) {
|
|
27455
|
+
return
|
|
27456
|
+
}
|
|
26627
27457
|
if (mouse_mode === 0 || mouse_mode === RED.state.SELECTING_NODE) {
|
|
26628
27458
|
if (mouse_mode === RED.state.SELECTING_NODE && selectNodesOptions && selectNodesOptions.filter) {
|
|
26629
27459
|
if (selectNodesOptions.filter(d)) {
|
|
@@ -26797,6 +27627,9 @@ RED.view = (function() {
|
|
|
26797
27627
|
if (RED.view.DEBUG) {
|
|
26798
27628
|
console.warn("groupMouseDown", { mouse_mode, point: mouse, event: d3.event });
|
|
26799
27629
|
}
|
|
27630
|
+
if (spacebarPressed) {
|
|
27631
|
+
return
|
|
27632
|
+
}
|
|
26800
27633
|
RED.contextMenu.hide();
|
|
26801
27634
|
focusView();
|
|
26802
27635
|
if (d3.event.button === 1) {
|
|
@@ -27256,6 +28089,15 @@ RED.view = (function() {
|
|
|
27256
28089
|
eventLayer.attr("transform","scale("+scaleFactor+")");
|
|
27257
28090
|
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
|
|
27258
28091
|
|
|
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
|
+
});
|
|
28100
|
+
|
|
27259
28101
|
// Don't bother redrawing nodes if we're drawing links
|
|
27260
28102
|
if (forceFullRedraw || showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
|
|
27261
28103
|
forceFullRedraw = false
|
|
@@ -29616,7 +30458,7 @@ RED.view = (function() {
|
|
|
29616
30458
|
selectNodes: function(options) {
|
|
29617
30459
|
$("#red-ui-workspace-tabs-shade").show();
|
|
29618
30460
|
$("#red-ui-palette-shade").show();
|
|
29619
|
-
$("
|
|
30461
|
+
$(".red-ui-sidebar-shade").show();
|
|
29620
30462
|
$("#red-ui-header-shade").show();
|
|
29621
30463
|
$("#red-ui-workspace").addClass("red-ui-workspace-select-mode");
|
|
29622
30464
|
|
|
@@ -29638,7 +30480,7 @@ RED.view = (function() {
|
|
|
29638
30480
|
clearSelection();
|
|
29639
30481
|
$("#red-ui-workspace-tabs-shade").hide();
|
|
29640
30482
|
$("#red-ui-palette-shade").hide();
|
|
29641
|
-
$("
|
|
30483
|
+
$(".red-ui-sidebar-shade").hide();
|
|
29642
30484
|
$("#red-ui-header-shade").hide();
|
|
29643
30485
|
$("#red-ui-workspace").removeClass("red-ui-workspace-select-mode");
|
|
29644
30486
|
resetMouseVars();
|
|
@@ -29707,7 +30549,283 @@ RED.view = (function() {
|
|
|
29707
30549
|
applySuggestedFlow
|
|
29708
30550
|
};
|
|
29709
30551
|
})();
|
|
29710
|
-
|
|
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() {
|
|
29711
30829
|
|
|
29712
30830
|
var annotations = {};
|
|
29713
30831
|
|
|
@@ -29907,139 +31025,202 @@ RED.view = (function() {
|
|
|
29907
31025
|
**/
|
|
29908
31026
|
|
|
29909
31027
|
|
|
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
|
-
|
|
30031
|
-
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
|
|
30035
|
-
|
|
30036
|
-
|
|
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
|
+
})
|
|
30037
31198
|
|
|
30038
31199
|
$("#red-ui-view-navigate").on("click", function(evt) {
|
|
30039
31200
|
evt.preventDefault();
|
|
30040
31201
|
toggle();
|
|
30041
31202
|
})
|
|
30042
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
|
+
// });
|
|
30043
31224
|
},
|
|
30044
31225
|
refresh: refreshNodes,
|
|
30045
31226
|
resize: resizeNavBorder,
|
|
@@ -31507,19 +32688,56 @@ RED.view.tools = (function() {
|
|
|
31507
32688
|
* limitations under the License.
|
|
31508
32689
|
**/
|
|
31509
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
|
+
}
|
|
31510
32732
|
|
|
31511
|
-
//$('#sidebar').tabs();
|
|
31512
|
-
var sidebar_tabs;
|
|
31513
|
-
var knownTabs = {};
|
|
31514
32733
|
|
|
31515
32734
|
// We store the current sidebar tab id in localStorage as 'last-sidebar-tab'
|
|
31516
32735
|
// This is restored when the editor is reloaded.
|
|
31517
|
-
// We use
|
|
32736
|
+
// We use sidebars.primary.tabs.onchange to update localStorage. However that will
|
|
31518
32737
|
// also get triggered when the first tab gets added to the tabs - typically
|
|
31519
32738
|
// the 'info' tab. So we use the following variable to store the retrieved
|
|
31520
32739
|
// value from localStorage before we start adding the actual tabs
|
|
31521
|
-
|
|
31522
|
-
|
|
32740
|
+
let lastSessionSelectedTabs = {}
|
|
31523
32741
|
|
|
31524
32742
|
function addTab(title,content,closeable,visible) {
|
|
31525
32743
|
var options;
|
|
@@ -31536,10 +32754,34 @@ RED.sidebar = (function() {
|
|
|
31536
32754
|
} else if (typeof title === "object") {
|
|
31537
32755
|
options = title;
|
|
31538
32756
|
}
|
|
32757
|
+
options.target = options.target || 'primary';
|
|
32758
|
+
let targetTabButtonIndex = -1 // Append to end by default
|
|
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
|
+
}
|
|
31539
32779
|
|
|
32780
|
+
|
|
32781
|
+
const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary;
|
|
31540
32782
|
delete options.closeable;
|
|
31541
32783
|
|
|
31542
|
-
options.wrapper = $('<div>',{style:"height:100%"}).appendTo(
|
|
32784
|
+
options.wrapper = $('<div>',{style:"height:100%"}).appendTo(targetSidebar.content)
|
|
31543
32785
|
options.wrapper.append(options.content);
|
|
31544
32786
|
options.wrapper.hide();
|
|
31545
32787
|
|
|
@@ -31548,11 +32790,12 @@ RED.sidebar = (function() {
|
|
|
31548
32790
|
}
|
|
31549
32791
|
|
|
31550
32792
|
if (options.toolbar) {
|
|
31551
|
-
|
|
32793
|
+
targetSidebar.footer.append(options.toolbar);
|
|
31552
32794
|
$(options.toolbar).hide();
|
|
31553
32795
|
}
|
|
31554
32796
|
var id = options.id;
|
|
31555
32797
|
|
|
32798
|
+
// console.log('menu', options.id, options.name)
|
|
31556
32799
|
RED.menu.addItem("menu-item-view-menu",{
|
|
31557
32800
|
id:"menu-item-view-menu-"+options.id,
|
|
31558
32801
|
label:options.name,
|
|
@@ -31565,208 +32808,315 @@ RED.sidebar = (function() {
|
|
|
31565
32808
|
options.iconClass = options.iconClass || "fa fa-square-o"
|
|
31566
32809
|
|
|
31567
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)
|
|
31568
32821
|
|
|
31569
|
-
|
|
31570
|
-
|
|
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)
|
|
31571
32842
|
}
|
|
31572
32843
|
}
|
|
31573
32844
|
|
|
31574
32845
|
function removeTab(id) {
|
|
31575
|
-
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
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
|
+
}
|
|
31579
32862
|
}
|
|
31580
|
-
delete knownTabs[id];
|
|
31581
|
-
RED.menu.removeItem("menu-item-view-menu-"+id);
|
|
31582
32863
|
}
|
|
31583
32864
|
|
|
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
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
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);
|
|
31639
32927
|
}
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31644
|
-
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
|
|
31661
|
-
|
|
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);
|
|
31662
32973
|
RED.events.emit("sidebar:resize");
|
|
31663
32974
|
}
|
|
31664
|
-
|
|
32975
|
+
sidebarSeparator.width = sidebar.container.width();
|
|
32976
|
+
},
|
|
32977
|
+
drag: function(event,ui) {
|
|
32978
|
+
var d = scaleFactor * (ui.position.left-sidebarSeparator.start);
|
|
31665
32979
|
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
31671
|
-
|
|
31672
|
-
|
|
31673
|
-
if (
|
|
31674
|
-
|
|
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
|
+
}
|
|
31675
33002
|
} else {
|
|
31676
|
-
|
|
33003
|
+
sidebarSeparator.closing = false
|
|
31677
33004
|
}
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
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");
|
|
31685
33025
|
}
|
|
31686
33026
|
});
|
|
33027
|
+
return separator
|
|
31687
33028
|
}
|
|
31688
33029
|
|
|
31689
|
-
function toggleSidebar(state) {
|
|
33030
|
+
function toggleSidebar(sidebar, state) {
|
|
31690
33031
|
if (!state) {
|
|
31691
|
-
|
|
33032
|
+
sidebar.container.hide()
|
|
33033
|
+
sidebar.separator.hide()
|
|
33034
|
+
sidebar.tabBar.find('button').removeClass('selected')
|
|
31692
33035
|
} else {
|
|
31693
|
-
|
|
31694
|
-
|
|
33036
|
+
sidebar.container.show()
|
|
33037
|
+
sidebar.separator.show()
|
|
31695
33038
|
}
|
|
31696
33039
|
RED.events.emit("sidebar:resize");
|
|
31697
33040
|
}
|
|
31698
33041
|
|
|
31699
33042
|
function showSidebar(id, skipShowSidebar) {
|
|
31700
33043
|
if (id === ":first") {
|
|
31701
|
-
|
|
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
|
|
31702
33054
|
}
|
|
31703
33055
|
if (id) {
|
|
31704
|
-
|
|
31705
|
-
|
|
31706
|
-
|
|
31707
|
-
|
|
31708
|
-
|
|
31709
|
-
|
|
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
|
+
}
|
|
31710
33076
|
}
|
|
31711
33077
|
}
|
|
31712
33078
|
}
|
|
31713
33079
|
|
|
31714
33080
|
function containsTab(id) {
|
|
31715
|
-
return
|
|
33081
|
+
return sidebars.primary.tabs.contains(id);
|
|
31716
33082
|
}
|
|
31717
33083
|
|
|
31718
|
-
function
|
|
31719
|
-
|
|
31720
|
-
|
|
31721
|
-
|
|
31722
|
-
|
|
31723
|
-
|
|
31724
|
-
|
|
31725
|
-
|
|
31726
|
-
tab.onchange.call(tab);
|
|
31727
|
-
}
|
|
31728
|
-
$(tab.wrapper).show();
|
|
31729
|
-
if (tab.toolbar) {
|
|
31730
|
-
$(tab.toolbar).show();
|
|
31731
|
-
}
|
|
31732
|
-
RED.settings.setLocal("last-sidebar-tab", tab.id)
|
|
31733
|
-
},
|
|
31734
|
-
onremove: function(tab) {
|
|
31735
|
-
$(tab.wrapper).hide();
|
|
31736
|
-
if (tab.onremove) {
|
|
31737
|
-
tab.onremove.call(tab);
|
|
31738
|
-
}
|
|
31739
|
-
},
|
|
31740
|
-
// minimumActiveTabWidth: 70,
|
|
31741
|
-
collapsible: true,
|
|
31742
|
-
onreorder: function(order) {
|
|
31743
|
-
RED.settings.set("editor.sidebar.order",order);
|
|
31744
|
-
},
|
|
31745
|
-
order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])
|
|
31746
|
-
// scrollable: true
|
|
31747
|
-
});
|
|
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);
|
|
31748
33092
|
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
|
|
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)
|
|
31752
33099
|
|
|
31753
33100
|
RED.actions.add("core:toggle-sidebar",function(state){
|
|
31754
33101
|
if (state === undefined) {
|
|
31755
|
-
RED.menu.toggleSelected(
|
|
33102
|
+
RED.menu.toggleSelected(sidebars.primary.menuToggle);
|
|
31756
33103
|
} else {
|
|
31757
|
-
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);
|
|
31758
33112
|
}
|
|
31759
33113
|
});
|
|
31760
|
-
RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar");
|
|
31761
|
-
|
|
31762
|
-
lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab")
|
|
31763
33114
|
|
|
31764
|
-
|
|
31765
|
-
|
|
31766
|
-
|
|
31767
|
-
|
|
31768
|
-
|
|
31769
|
-
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
|
+
})
|
|
31770
33120
|
}
|
|
31771
33121
|
|
|
31772
33122
|
return {
|
|
@@ -31814,7 +33164,6 @@ RED.palette = (function() {
|
|
|
31814
33164
|
];
|
|
31815
33165
|
|
|
31816
33166
|
var categoryContainers = {};
|
|
31817
|
-
var sidebarControls;
|
|
31818
33167
|
|
|
31819
33168
|
let paletteState = { filter: "", collapsed: [] };
|
|
31820
33169
|
|
|
@@ -32091,6 +33440,7 @@ RED.palette = (function() {
|
|
|
32091
33440
|
width: "300px",
|
|
32092
33441
|
content: "hi",
|
|
32093
33442
|
delay: { show: 750, hide: 50 }
|
|
33443
|
+
// direction: "left"
|
|
32094
33444
|
});
|
|
32095
33445
|
|
|
32096
33446
|
d.data('popover',popover);
|
|
@@ -32113,7 +33463,8 @@ RED.palette = (function() {
|
|
|
32113
33463
|
revert: 'invalid',
|
|
32114
33464
|
revertDuration: 200,
|
|
32115
33465
|
containment:'#red-ui-main-container',
|
|
32116
|
-
start: function() {
|
|
33466
|
+
start: function(e, ui) {
|
|
33467
|
+
ui.helper.css('z-index', 1000);
|
|
32117
33468
|
dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked);
|
|
32118
33469
|
paletteWidth = $("#red-ui-palette").width();
|
|
32119
33470
|
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
|
|
@@ -32139,7 +33490,9 @@ RED.palette = (function() {
|
|
|
32139
33490
|
},
|
|
32140
33491
|
drag: function(e,ui) {
|
|
32141
33492
|
var paletteNode = getPaletteNode(nt);
|
|
32142
|
-
ui.originalPosition.left
|
|
33493
|
+
console.log(ui.originalPosition.left, paletteNode.offset().left)
|
|
33494
|
+
// ui.originalPosition.left = paletteNode.offset().left;
|
|
33495
|
+
// console.log(paletteNode.offset())
|
|
32143
33496
|
if (dropEnabled) {
|
|
32144
33497
|
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
|
|
32145
33498
|
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10;
|
|
@@ -32388,11 +33741,24 @@ RED.palette = (function() {
|
|
|
32388
33741
|
|
|
32389
33742
|
function init() {
|
|
32390
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
|
+
|
|
32391
33758
|
$('<img src="red/images/spin.svg" class="red-ui-palette-spinner hide"/>').appendTo("#red-ui-palette");
|
|
32392
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");
|
|
32393
33760
|
$('<div id="red-ui-palette-container" class="red-ui-palette-scroll hide"></div>').appendTo("#red-ui-palette");
|
|
32394
|
-
$('<div
|
|
32395
|
-
$('<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");
|
|
32396
33762
|
|
|
32397
33763
|
$("#red-ui-palette > .red-ui-palette-spinner").show();
|
|
32398
33764
|
|
|
@@ -32451,19 +33817,6 @@ RED.palette = (function() {
|
|
|
32451
33817
|
}
|
|
32452
33818
|
});
|
|
32453
33819
|
|
|
32454
|
-
sidebarControls = $('<div class="red-ui-sidebar-control-left"><i class="fa fa-chevron-left"></i></div>').appendTo($("#red-ui-palette"));
|
|
32455
|
-
RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette");
|
|
32456
|
-
|
|
32457
|
-
sidebarControls.on("click", function() {
|
|
32458
|
-
RED.menu.toggleSelected("menu-item-palette");
|
|
32459
|
-
})
|
|
32460
|
-
$("#red-ui-palette").on("mouseenter", function() {
|
|
32461
|
-
sidebarControls.toggle("slide", { direction: "left" }, 200);
|
|
32462
|
-
})
|
|
32463
|
-
$("#red-ui-palette").on("mouseleave", function() {
|
|
32464
|
-
sidebarControls.stop(false,true);
|
|
32465
|
-
sidebarControls.hide();
|
|
32466
|
-
})
|
|
32467
33820
|
var userCategories = [];
|
|
32468
33821
|
if (RED.settings.paletteCategories) {
|
|
32469
33822
|
userCategories = RED.settings.paletteCategories;
|
|
@@ -32485,7 +33838,7 @@ RED.palette = (function() {
|
|
|
32485
33838
|
}
|
|
32486
33839
|
});
|
|
32487
33840
|
|
|
32488
|
-
var paletteFooterButtons = $('<span class="button-group"></span>').appendTo(
|
|
33841
|
+
var paletteFooterButtons = $('<span class="button-group"></span>').appendTo(toolbar);
|
|
32489
33842
|
var paletteCollapseAll = $('<button type="button" class="red-ui-footer-button"><i class="fa fa-angle-double-up"></i></button>').appendTo(paletteFooterButtons);
|
|
32490
33843
|
paletteCollapseAll.on("click", function(e) {
|
|
32491
33844
|
e.preventDefault();
|
|
@@ -32508,13 +33861,7 @@ RED.palette = (function() {
|
|
|
32508
33861
|
});
|
|
32509
33862
|
RED.popover.tooltip(paletteExpandAll,RED._('palette.actions.expand-all'));
|
|
32510
33863
|
|
|
32511
|
-
|
|
32512
|
-
if (state === undefined) {
|
|
32513
|
-
RED.menu.toggleSelected("menu-item-palette");
|
|
32514
|
-
} else {
|
|
32515
|
-
togglePalette(state);
|
|
32516
|
-
}
|
|
32517
|
-
});
|
|
33864
|
+
|
|
32518
33865
|
|
|
32519
33866
|
try {
|
|
32520
33867
|
paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}');
|
|
@@ -32532,18 +33879,6 @@ RED.palette = (function() {
|
|
|
32532
33879
|
}, 10000)
|
|
32533
33880
|
}
|
|
32534
33881
|
|
|
32535
|
-
function togglePalette(state) {
|
|
32536
|
-
if (!state) {
|
|
32537
|
-
$("#red-ui-main-container").addClass("red-ui-palette-closed");
|
|
32538
|
-
sidebarControls.hide();
|
|
32539
|
-
sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left");
|
|
32540
|
-
} else {
|
|
32541
|
-
$("#red-ui-main-container").removeClass("red-ui-palette-closed");
|
|
32542
|
-
sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left");
|
|
32543
|
-
}
|
|
32544
|
-
setTimeout(function() { $(window).trigger("resize"); } ,200);
|
|
32545
|
-
}
|
|
32546
|
-
|
|
32547
33882
|
function getCategories() {
|
|
32548
33883
|
var categories = [];
|
|
32549
33884
|
$("#red-ui-palette-container .red-ui-palette-category").each(function(i,d) {
|
|
@@ -32691,9 +34026,10 @@ RED.sidebar.info = (function() {
|
|
|
32691
34026
|
|
|
32692
34027
|
RED.sidebar.addTab({
|
|
32693
34028
|
id: "info",
|
|
34029
|
+
// target: "secondary",
|
|
32694
34030
|
label: RED._("sidebar.info.label"),
|
|
32695
34031
|
name: RED._("sidebar.info.name"),
|
|
32696
|
-
|
|
34032
|
+
icon: "red/images/explorer.svg",
|
|
32697
34033
|
action:"core:show-info-tab",
|
|
32698
34034
|
content: content,
|
|
32699
34035
|
pinned: true,
|
|
@@ -32729,6 +34065,8 @@ RED.sidebar.info = (function() {
|
|
|
32729
34065
|
tips.stop();
|
|
32730
34066
|
}
|
|
32731
34067
|
|
|
34068
|
+
resizeStack();
|
|
34069
|
+
|
|
32732
34070
|
}
|
|
32733
34071
|
|
|
32734
34072
|
function show() {
|
|
@@ -45677,6 +47015,8 @@ RED.eventLog = (function() {
|
|
|
45677
47015
|
}
|
|
45678
47016
|
|
|
45679
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);
|
|
45680
47020
|
if (stack.length > 0) {
|
|
45681
47021
|
var tray = stack[stack.length-1];
|
|
45682
47022
|
if (tray.options.maximized || tray.width > $("#red-ui-editor-stack").position().left-8) {
|
|
@@ -48838,6 +50178,12 @@ RED.search = (function() {
|
|
|
48838
50178
|
$('<div>',{class:"red-ui-search-result-node-type"}).text(node.type).appendTo(contentDiv);
|
|
48839
50179
|
$('<div>',{class:"red-ui-search-result-node-id"}).text(node.id).appendTo(contentDiv);
|
|
48840
50180
|
|
|
50181
|
+
div.on("mouseover", function(evt) {
|
|
50182
|
+
if ( node.z == RED.workspaces.active() ) {
|
|
50183
|
+
RED.view.reveal(node.id)
|
|
50184
|
+
}
|
|
50185
|
+
});
|
|
50186
|
+
|
|
48841
50187
|
div.on("click", function(evt) {
|
|
48842
50188
|
evt.preventDefault();
|
|
48843
50189
|
currentIndex = i;
|
|
@@ -48916,8 +50262,7 @@ RED.search = (function() {
|
|
|
48916
50262
|
$("#red-ui-header-shade").show();
|
|
48917
50263
|
$("#red-ui-editor-shade").show();
|
|
48918
50264
|
$("#red-ui-palette-shade").show();
|
|
48919
|
-
$("
|
|
48920
|
-
$("#red-ui-sidebar-separator").hide();
|
|
50265
|
+
$(".red-ui-sidebar-shade").show();
|
|
48921
50266
|
|
|
48922
50267
|
if (dialog === null) {
|
|
48923
50268
|
createDialog();
|
|
@@ -48941,8 +50286,7 @@ RED.search = (function() {
|
|
|
48941
50286
|
$("#red-ui-header-shade").hide();
|
|
48942
50287
|
$("#red-ui-editor-shade").hide();
|
|
48943
50288
|
$("#red-ui-palette-shade").hide();
|
|
48944
|
-
$("
|
|
48945
|
-
$("#red-ui-sidebar-separator").show();
|
|
50289
|
+
$(".red-ui-sidebar-shade").hide();
|
|
48946
50290
|
if (dialog !== null) {
|
|
48947
50291
|
dialog.slideUp(200,function() {
|
|
48948
50292
|
searchInput.searchBox('value','');
|
|
@@ -49042,7 +50386,7 @@ RED.search = (function() {
|
|
|
49042
50386
|
$("#red-ui-header-shade").on('mousedown',hide);
|
|
49043
50387
|
$("#red-ui-editor-shade").on('mousedown',hide);
|
|
49044
50388
|
$("#red-ui-palette-shade").on('mousedown',hide);
|
|
49045
|
-
$("
|
|
50389
|
+
$(".red-ui-sidebar-shade").on('mousedown',hide);
|
|
49046
50390
|
|
|
49047
50391
|
$("#red-ui-view-searchtools-close").on("click", function close() {
|
|
49048
50392
|
clearActiveSearch();
|
|
@@ -49533,8 +50877,7 @@ RED.actionList = (function() {
|
|
|
49533
50877
|
$("#red-ui-header-shade").show();
|
|
49534
50878
|
$("#red-ui-editor-shade").show();
|
|
49535
50879
|
$("#red-ui-palette-shade").show();
|
|
49536
|
-
$("
|
|
49537
|
-
$("#red-ui-sidebar-separator").hide();
|
|
50880
|
+
$(".red-ui-sidebar-shade").show();
|
|
49538
50881
|
if (dialog === null) {
|
|
49539
50882
|
createDialog();
|
|
49540
50883
|
}
|
|
@@ -49568,8 +50911,7 @@ RED.actionList = (function() {
|
|
|
49568
50911
|
$("#red-ui-header-shade").hide();
|
|
49569
50912
|
$("#red-ui-editor-shade").hide();
|
|
49570
50913
|
$("#red-ui-palette-shade").hide();
|
|
49571
|
-
$("
|
|
49572
|
-
$("#red-ui-sidebar-separator").show();
|
|
50914
|
+
$(".red-ui-sidebar-shade").hide();
|
|
49573
50915
|
if (dialog !== null) {
|
|
49574
50916
|
dialog.slideUp(200,function() {
|
|
49575
50917
|
searchInput.searchBox('value','');
|
|
@@ -49601,7 +50943,7 @@ RED.actionList = (function() {
|
|
|
49601
50943
|
$("#red-ui-header-shade").on('mousedown',hide);
|
|
49602
50944
|
$("#red-ui-editor-shade").on('mousedown',hide);
|
|
49603
50945
|
$("#red-ui-palette-shade").on('mousedown',hide);
|
|
49604
|
-
$("
|
|
50946
|
+
$(".red-ui-sidebar-shade").on('mousedown',hide);
|
|
49605
50947
|
}
|
|
49606
50948
|
|
|
49607
50949
|
return {
|
|
@@ -52423,7 +53765,7 @@ RED.userSettings = (function() {
|
|
|
52423
53765
|
});
|
|
52424
53766
|
settingsContent.i18n();
|
|
52425
53767
|
settingsTabs.activateTab("red-ui-settings-tab-"+(initialTab||'view'))
|
|
52426
|
-
$("
|
|
53768
|
+
$(".red-ui-sidebar-shade").show();
|
|
52427
53769
|
},
|
|
52428
53770
|
close: function() {
|
|
52429
53771
|
settingsVisible = false;
|
|
@@ -52432,7 +53774,7 @@ RED.userSettings = (function() {
|
|
|
52432
53774
|
pane.close();
|
|
52433
53775
|
}
|
|
52434
53776
|
});
|
|
52435
|
-
$("
|
|
53777
|
+
$(".red-ui-sidebar-shade").hide();
|
|
52436
53778
|
|
|
52437
53779
|
},
|
|
52438
53780
|
show: function() {}
|
|
@@ -55225,7 +56567,7 @@ RED.projects.settings = (function() {
|
|
|
55225
56567
|
});
|
|
55226
56568
|
settingsContent.i18n();
|
|
55227
56569
|
settingsTabs.activateTab("red-ui-project-settings-tab-"+(initialTab||'main'))
|
|
55228
|
-
$("
|
|
56570
|
+
$(".red-ui-sidebar-shade").show();
|
|
55229
56571
|
},
|
|
55230
56572
|
close: function() {
|
|
55231
56573
|
settingsVisible = false;
|
|
@@ -55234,7 +56576,7 @@ RED.projects.settings = (function() {
|
|
|
55234
56576
|
pane.close();
|
|
55235
56577
|
}
|
|
55236
56578
|
});
|
|
55237
|
-
$("
|
|
56579
|
+
$(".red-ui-sidebar-shade").hide();
|
|
55238
56580
|
|
|
55239
56581
|
},
|
|
55240
56582
|
show: function() {}
|
|
@@ -59248,10 +60590,15 @@ RED.touch.radialMenu = (function() {
|
|
|
59248
60590
|
|
|
59249
60591
|
function listTour() {
|
|
59250
60592
|
return [
|
|
60593
|
+
{
|
|
60594
|
+
id: "5_0",
|
|
60595
|
+
label: "5.0",
|
|
60596
|
+
path: "./tours/welcome.js"
|
|
60597
|
+
},
|
|
59251
60598
|
{
|
|
59252
60599
|
id: "4_1",
|
|
59253
60600
|
label: "4.1",
|
|
59254
|
-
path: "./tours/welcome.js"
|
|
60601
|
+
path: "./tours/4.1/welcome.js"
|
|
59255
60602
|
},
|
|
59256
60603
|
{
|
|
59257
60604
|
id: "4_0",
|