1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Add flow navigator widget

This commit is contained in:
Nick O'Leary 2018-06-08 23:32:17 +01:00
parent f6274445a2
commit 17c5fdf0d5
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
6 changed files with 180 additions and 4 deletions

View File

@ -144,6 +144,7 @@ module.exports = function(grunt) {
"editor/js/ui/keyboard.js", "editor/js/ui/keyboard.js",
"editor/js/ui/workspaces.js", "editor/js/ui/workspaces.js",
"editor/js/ui/view.js", "editor/js/ui/view.js",
"editor/js/ui/view-navigator.js",
"editor/js/ui/sidebar.js", "editor/js/ui/sidebar.js",
"editor/js/ui/palette.js", "editor/js/ui/palette.js",
"editor/js/ui/tab-info.js", "editor/js/ui/tab-info.js",

View File

@ -0,0 +1,164 @@
/**
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.view.navigator = (function() {
var nav_scale = 25;
var nav_width = 5000/nav_scale;
var nav_height = 5000/nav_scale;
var navContainer;
var navBox;
var navBorder;
var navVis;
var scrollPos;
var scaleFactor;
var chartSize;
var dimensions;
var isDragging;
var isShowing = false;
function refreshNodes() {
if (!isShowing) {
return;
}
var navNode = navVis.selectAll(".navnode").data(RED.view.getActiveNodes(),function(d){return d.id});
navNode.exit().remove();
navNode.enter().insert("rect")
.attr('class','navnode')
.attr("pointer-events", "none");
navNode.each(function(d) {
d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale })
.attr("y",function(d) { return (d.y-d.h/2)/nav_scale })
.attr("width",function(d) { return Math.max(9,d.w/nav_scale) })
.attr("height",function(d) { return Math.max(3,d.h/nav_scale) })
.attr("fill",function(d) { return d._def.color;})
});
}
function onScroll() {
if (!isDragging) {
resizeNavBorder();
}
}
function resizeNavBorder() {
if (navBorder) {
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
navBorder.attr('x',scrollPos[0]/nav_scale)
.attr('y',scrollPos[1]/nav_scale)
.attr('width',chartSize[0]/nav_scale/scaleFactor)
.attr('height',chartSize[1]/nav_scale/scaleFactor)
}
}
return {
init: function() {
$(window).resize(resizeNavBorder);
RED.events.on("sidebar:resize",resizeNavBorder);
var hideTimeout;
navContainer = $('<div>').css({
"position":"absolute",
"bottom":$("#workspace-footer").height(),
"right":0,
zIndex: 1
}).appendTo("#workspace").hide();
navBox = d3.select(navContainer[0])
.append("svg:svg")
.attr("width", nav_width)
.attr("height", nav_height)
.attr("pointer-events", "all")
.style({
position: "absolute",
bottom: 0,
right:0,
zIndex: 101,
"border-left": "1px solid #ccc",
"border-top": "1px solid #ccc",
background: "rgba(245,245,245,0.5)",
"box-shadow": "-1px 0 3px rgba(0,0,0,0.1)"
});
navBox.append("rect").attr("x",0).attr("y",0).attr("width",nav_width).attr("height",nav_height).style({
fill:"none",
stroke:"none",
pointerEvents:"all"
}).on("mousedown", function() {
// Update these in case they have changed
scaleFactor = RED.view.scale();
chartSize = [ $("#chart").width(), $("#chart").height()];
dimensions = [chartSize[0]/nav_scale/scaleFactor, chartSize[1]/nav_scale/scaleFactor];
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
isDragging = true;
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mousemove", function() {
if (!isDragging) { return }
if (d3.event.buttons === 0) {
isDragging = false;
return;
}
var newX = Math.max(0,Math.min(d3.event.offsetX+dimensions[0]/2,nav_width)-dimensions[0]);
var newY = Math.max(0,Math.min(d3.event.offsetY+dimensions[1]/2,nav_height)-dimensions[1]);
navBorder.attr('x',newX).attr('y',newY);
$("#chart").scrollLeft(newX*nav_scale*scaleFactor);
$("#chart").scrollTop(newY*nav_scale*scaleFactor);
}).on("mouseup", function() {
isDragging = false;
})
navBorder = navBox.append("rect")
.attr("stroke-dasharray","5,5")
.attr("pointer-events", "none")
.style({
stroke: "#999",
strokeWidth: 1,
fill: "white",
});
navVis = navBox.append("svg:g")
$("#btn-navigate").click(function(evt) {
evt.preventDefault();
if (!isShowing) {
isShowing = true;
$("#btn-navigate").addClass("selected");
resizeNavBorder();
refreshNodes();
$("#chart").on("scroll",onScroll);
navContainer.fadeIn(200);
} else {
isShowing = false;
navContainer.fadeOut(100);
$("#chart").off("scroll",onScroll);
$("#btn-navigate").removeClass("selected");
}
})
},
refresh: refreshNodes,
resize: resizeNavBorder
}
})();

View File

@ -332,6 +332,8 @@ RED.view = (function() {
redraw(); redraw();
}); });
RED.view.navigator.init();
$("#btn-zoom-out").click(function() {zoomOut();}); $("#btn-zoom-out").click(function() {zoomOut();});
$("#btn-zoom-zero").click(function() {zoomZero();}); $("#btn-zoom-zero").click(function() {zoomZero();});
$("#btn-zoom-in").click(function() {zoomIn();}); $("#btn-zoom-in").click(function() {zoomIn();});
@ -1060,17 +1062,20 @@ RED.view = (function() {
function zoomIn() { function zoomIn() {
if (scaleFactor < 2) { if (scaleFactor < 2) {
scaleFactor += 0.1; scaleFactor += 0.1;
RED.view.navigator.resize();
redraw(); redraw();
} }
} }
function zoomOut() { function zoomOut() {
if (scaleFactor > 0.3) { if (scaleFactor > 0.3) {
scaleFactor -= 0.1; scaleFactor -= 0.1;
RED.view.navigator.resize();
redraw(); redraw();
} }
} }
function zoomZero() { function zoomZero() {
scaleFactor = 1; scaleFactor = 1;
RED.view.navigator.resize();
redraw(); redraw();
} }
@ -2542,7 +2547,7 @@ RED.view = (function() {
} }
).classed("link_selected", false); ).classed("link_selected", false);
} }
RED.view.navigator.refresh();
if (d3.event) { if (d3.event) {
d3.event.preventDefault(); d3.event.preventDefault();
} }
@ -2816,7 +2821,9 @@ RED.view = (function() {
gridSize = Math.max(5,v); gridSize = Math.max(5,v);
updateGrid(); updateGrid();
} }
},
getActiveNodes: function() {
return activeNodes;
} }
}; };
})(); })();

View File

@ -203,7 +203,7 @@
height: 25px; height: 25px;
line-height: 23px; line-height: 23px;
padding: 0 10px; padding: 0 10px;
user-select: none;
.button-group:not(:last-child) { .button-group:not(:last-child) {
margin-right: 5px; margin-right: 5px;
@ -227,6 +227,7 @@
font-size: 11px; font-size: 11px;
line-height: 17px; line-height: 17px;
height: 18px; height: 18px;
width: 18px;
&.text-button { &.text-button {
width: auto; width: auto;
padding: 0 5px; padding: 0 5px;

View File

@ -47,7 +47,9 @@
.workspace-footer-button { .workspace-footer-button {
@include component-footer-button; @include component-footer-button;
} }
.workspace-footer-button-toggle {
@include component-footer-button-toggle;
}
#workspace-footer { #workspace-footer {
@include component-footer; @include component-footer;
} }

View File

@ -51,6 +51,7 @@
<a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a> <a class="workspace-footer-button" id="btn-zoom-out" href="#"><i class="fa fa-minus"></i></a>
<a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a> <a class="workspace-footer-button" id="btn-zoom-zero" href="#"><i class="fa fa-circle-o"></i></a>
<a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a> <a class="workspace-footer-button" id="btn-zoom-in" href="#"><i class="fa fa-plus"></i></a>
<a class="workspace-footer-button-toggle" id="btn-navigate" href="#"><i class="fa fa-map-o"></i></a>
</div> </div>
<div id="editor-shade" class="hide"></div> <div id="editor-shade" class="hide"></div>
</div> </div>