diff --git a/Gruntfile.js b/Gruntfile.js
index 3738e60b4..6d53e4d29 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -144,6 +144,7 @@ module.exports = function(grunt) {
"editor/js/ui/keyboard.js",
"editor/js/ui/workspaces.js",
"editor/js/ui/view.js",
+ "editor/js/ui/view-navigator.js",
"editor/js/ui/sidebar.js",
"editor/js/ui/palette.js",
"editor/js/ui/tab-info.js",
diff --git a/editor/js/ui/view-navigator.js b/editor/js/ui/view-navigator.js
new file mode 100644
index 000000000..da1908982
--- /dev/null
+++ b/editor/js/ui/view-navigator.js
@@ -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 = $('
').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
+ }
+
+
+})();
diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js
index d6c1bfa8c..6a8dd77c4 100644
--- a/editor/js/ui/view.js
+++ b/editor/js/ui/view.js
@@ -332,6 +332,8 @@ RED.view = (function() {
redraw();
});
+ RED.view.navigator.init();
+
$("#btn-zoom-out").click(function() {zoomOut();});
$("#btn-zoom-zero").click(function() {zoomZero();});
$("#btn-zoom-in").click(function() {zoomIn();});
@@ -1060,17 +1062,20 @@ RED.view = (function() {
function zoomIn() {
if (scaleFactor < 2) {
scaleFactor += 0.1;
+ RED.view.navigator.resize();
redraw();
}
}
function zoomOut() {
if (scaleFactor > 0.3) {
scaleFactor -= 0.1;
+ RED.view.navigator.resize();
redraw();
}
}
function zoomZero() {
scaleFactor = 1;
+ RED.view.navigator.resize();
redraw();
}
@@ -2542,7 +2547,7 @@ RED.view = (function() {
}
).classed("link_selected", false);
}
-
+ RED.view.navigator.refresh();
if (d3.event) {
d3.event.preventDefault();
}
@@ -2816,7 +2821,9 @@ RED.view = (function() {
gridSize = Math.max(5,v);
updateGrid();
}
+ },
+ getActiveNodes: function() {
+ return activeNodes;
}
-
};
})();
diff --git a/editor/sass/mixins.scss b/editor/sass/mixins.scss
index 7201ef544..36d08c948 100644
--- a/editor/sass/mixins.scss
+++ b/editor/sass/mixins.scss
@@ -203,7 +203,7 @@
height: 25px;
line-height: 23px;
padding: 0 10px;
-
+ user-select: none;
.button-group:not(:last-child) {
margin-right: 5px;
@@ -227,6 +227,7 @@
font-size: 11px;
line-height: 17px;
height: 18px;
+ width: 18px;
&.text-button {
width: auto;
padding: 0 5px;
diff --git a/editor/sass/workspace.scss b/editor/sass/workspace.scss
index 59b8fcf75..14b6eebb0 100644
--- a/editor/sass/workspace.scss
+++ b/editor/sass/workspace.scss
@@ -47,7 +47,9 @@
.workspace-footer-button {
@include component-footer-button;
}
-
+.workspace-footer-button-toggle {
+ @include component-footer-button-toggle;
+}
#workspace-footer {
@include component-footer;
}
diff --git a/editor/templates/index.mst b/editor/templates/index.mst
index b8d0ac0c0..972258a55 100644
--- a/editor/templates/index.mst
+++ b/editor/templates/index.mst
@@ -51,6 +51,7 @@
+