azkaban-uncached

Details

diff --git a/src/web/js/graph/azkaban.context.menu.js b/src/web/js/graph/azkaban.context.menu.js
new file mode 100644
index 0000000..7bca798
--- /dev/null
+++ b/src/web/js/graph/azkaban.context.menu.js
@@ -0,0 +1,97 @@
+$.namespace('azkaban');
+
+azkaban.ContextMenuView = Backbone.View.extend({
+	events :  {
+	},
+	initialize : function(settings) {
+		var div = this.el;
+		$('body').click(function(e) {
+			$(".contextMenu").remove();
+		});
+		$('body').bind("contextmenu", function(e) {$(".contextMenu").remove()});
+	},
+	show : function(evt, menu) {
+		console.log("Show context menu");
+		$(".contextMenu").remove();
+		var x = evt.pageX;
+		var y = evt.pageY;
+
+		var contextMenu = this.setupMenu(menu);
+		$(contextMenu).css({top: y, left: x});
+		
+		$(this.el).after(contextMenu);
+	},
+	hide : function(evt) {
+		console.log("Hide context menu");
+		$(".contextMenu").remove();
+	},
+	handleClick: function(evt) {
+		console.log("handling click");
+	},
+	setupMenu: function(menu) {
+		var contextMenu = document.createElement("div");
+		$(contextMenu).addClass("contextMenu");
+		var ul = document.createElement("ul");
+		$(contextMenu).append(ul);
+
+		for (var i = 0; i < menu.length; ++i) {
+			var menuItem = document.createElement("li");
+			if (menu[i].break) {
+				$(menuItem).addClass("break");
+			}
+			else {
+				var title = menu[i].title;
+				var callback = menu[i].callback;
+				$(menuItem).addClass("menuitem");
+				$(menuItem).text(title);
+				menuItem.callback = callback;
+				$(menuItem).click(function() { 
+					$(contextMenu).hide(); 
+					this.callback.call();});
+					
+				if (menu[i].submenu) {
+					var expandSymbol = document.createElement("div");
+					$(expandSymbol).addClass("expandSymbol");
+					$(menuItem).append(expandSymbol);
+					
+					var subMenu = this.setupMenu(menu[i].submenu);
+					$(subMenu).addClass("subMenu");
+					subMenu.parent = contextMenu;
+					menuItem.subMenu = subMenu;
+					$(subMenu).hide();
+					$(this.el).after(subMenu);
+					
+					$(menuItem).mouseenter(function() {
+						$(".subMenu").hide();
+						var menuItem = this;
+						menuItem.selected = true;
+						setTimeout(function() {
+							if (menuItem.selected) {
+								var offset = $(menuItem).offset();
+								var left = offset.left;
+								var top = offset.top;
+								var width = $(menuItem).width();
+								var subMenu = menuItem.subMenu;
+								
+								var newLeft = left + width - 5;
+								$(subMenu).css({left: newLeft, top: top});
+								$(subMenu).show();
+							}
+						}, 500);
+					});
+					$(menuItem).mouseleave(function() {this.selected = false;});
+				}
+			}
+
+			$(ul).append(menuItem);
+		}
+
+		return contextMenu;
+	}
+});
+
+var contextMenuView;
+$(function() {
+	contextMenuView = new azkaban.ContextMenuView({el:$('#contextMenu')});
+	contextMenuView.hide();
+});
\ No newline at end of file
diff --git a/src/web/js/graph/azkaban.svg.graph.view.js b/src/web/js/graph/azkaban.svg.graph.view.js
index 09d5228..ed1bb04 100644
--- a/src/web/js/graph/azkaban.svg.graph.view.js
+++ b/src/web/js/graph/azkaban.svg.graph.view.js
@@ -17,7 +17,8 @@ function addClass(el, name) {
 function removeClass(el, name) {
 	if (hasClass(el, name)) {
 		var classes = el.getAttribute("class");
-		el.setAttribute("class", classes.replace(new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
+		el.setAttribute("class", classes.replace(
+				new RegExp('(\\s|^)'+name+'(\\s|$)'),' ').replace(/^\s+|\s+$/g, ''));
 	}
 }
 
@@ -28,6 +29,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		"contextmenu g" : "handleRightClick",
 		"contextmenu polyline": "handleRightClick"
 	},
+	
 	initialize: function(settings) {
 		this.model.bind('change:selected', this.changeSelected, this);
 		this.model.bind('centerNode', this.centerNode, this);
@@ -58,6 +60,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			this.render();
 		}
 	},
+	
 	initializeDefs: function(self) {
 		var def = document.createElementNS(svgns, 'defs');
 		def.setAttribute("id", "buttonDefs");
@@ -80,6 +83,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		
 		this.svgGraph.appendChild(def);
 	},
+	
 	render: function(self) {
 		console.log("graph render");
 
@@ -133,7 +137,11 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			this.drawEdge(this, edges[i]);
 		}
 		
-		this.model.set({"flowId":data.flowId, "nodes": this.nodes, "edges": edges});
+		this.model.set({
+			"flowId": data.flowId, 
+			"nodes": this.nodes, 
+			"edges": edges
+		});
 		
 		var margin = this.graphMargin;
 		bounds.minX = bounds.minX ? bounds.minX - margin : -margin;
@@ -152,6 +160,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 		this.graphBounds = bounds;
 		this.resetPanZoom(0);
 	},
+	
 	handleDisabledChange: function(evt) {
 		var disabledMap = this.model.get("disabled");
 
@@ -167,6 +176,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
+	
 	assignInitialStatus: function(evt) {
 		var data = this.model.get("data");
 		for (var i = 0; i < data.nodes.length; ++i) {
@@ -180,6 +190,7 @@ azkaban.SvgGraphView = Backbone.View.extend({
 			}
 		}
 	},
+	
 	changeSelected: function(self) {
 		console.log("change selected");
 		var selected = this.model.get("selected");
diff --git a/src/web/js/graph/svgNavigate.js b/src/web/js/graph/svgNavigate.js
new file mode 100644
index 0000000..d1f7892
--- /dev/null
+++ b/src/web/js/graph/svgNavigate.js
@@ -0,0 +1,378 @@
+(function($) {	
+
+	var mouseUp = function(evt) {
+		if (evt.button > 1) {
+			return;
+		}
+		var target = evt.target;
+		target.mx = evt.clientX;
+		target.my = evt.clientY;
+		target.mDown = false;
+	}
+	
+	var mouseDown = function(evt) {
+		if (evt.button > 1) {
+                        return;
+                }
+		//alert("mouseDown");
+		var target = evt.target;
+		target.mx = evt.clientX;
+		target.my = evt.clientY;
+		target.mDown = true;
+	}
+	
+	var mouseOut = function(evt) {
+		var target = evt.target;
+		target.mx = evt.clientX;
+		target.my = evt.clientY;
+		target.mDown = false;
+	}
+	
+	var mouseMove = function(evt) {
+		var target = evt.target;
+		if (target.mDown) {
+			var dx = evt.clientX - target.mx;
+			var dy = evt.clientY - target.my;
+			
+			evt.dragX = dx;
+			evt.dragY = dy;
+			mouseDrag(evt);
+		}
+		
+		target.mx = evt.clientX;
+		target.my = evt.clientY;
+	}
+	
+	var mouseDrag = function(evt) {
+		//alert("mouseDragged ");
+		translateDeltaGraph(evt.target, evt.dragX, evt.dragY);
+	}
+	
+	var mouseScrolled = function(evt) {
+		//alert("scroll");
+		if (!evt) {
+			evt = window.event;
+		}
+		var target = evt.target;
+		
+		
+		var leftOffset = 0;
+		var topOffset = 0;
+		if (!target.marker) {
+			while (!target.farthestViewportElement) {
+				target = target.parentNode;
+			}
+
+			target = target.farthestViewportElement;
+		}
+		
+		// Trackball/trackpad vs wheel. Need to accommodate
+		var delta = 0;
+		if (evt.wheelDelta) {
+			if (evt.wheelDelta > 0) {
+				delta = Math.ceil(evt.wheelDelta / 120);
+			}
+			else {
+				delta = Math.floor(evt.wheelDelta / 120);
+			}
+		}
+		else if (evt.detail) {
+			if (evt.detail > 0) {
+				delta = -Math.ceil(evt.detail / 3);
+			}
+			else {
+				delta = -Math.floor(evt.detail / 3);
+			}
+		}
+		
+		var zoomLevel = boundZoomLevel(target, target.zoomIndex + delta);
+		target.zoomIndex = zoomLevel;
+		var scale = target.zoomLevels[zoomLevel];
+		
+		var y = evt.layerY;
+		var x = evt.layerX;
+
+		scaleGraph(target, scale, x, y);
+	}
+	
+	this.boundZoomLevel = function(target, level) {
+		if (level >= target.settings.zoomNumLevels) {
+			return target.settings.zoomNumLevels - 1;
+		}
+		else if (level <= 0 ) {
+			return 0;
+		}
+		
+		return level;
+	}
+	
+	this.scaleGraph = function(target, scale, x, y) {
+		var sfactor = scale/target.scale;
+		target.scale = scale;
+		
+		target.translateX = sfactor*target.translateX + x - sfactor*x;
+		target.translateY = sfactor*target.translateY + y - sfactor*y;
+		
+		if (target.model) {
+			target.model.trigger("scaled");
+		}
+		retransform(target);
+	}
+	
+	this.translateDeltaGraph = function(target, x, y) {
+		target.translateX += x;
+		target.translateY += y;
+		if (target.model) {
+			target.model.trigger("panned");
+		}
+		retransform(target);
+	}
+	
+	this.retransform = function(target) {
+		//$(target).children('g').attr("transform", "translate(" + target.translateX + "," + target.translateY + ") scale(" + target.scale + ")");
+		var gs = target.childNodes;
+		
+		var transformString = "translate(" + target.translateX + "," + target.translateY + ") scale(" + target.scale + ")";
+		for (var i = 0; i < gs.length; ++i) {
+			var g = gs[i];
+			if (g.nodeName == 'g') {
+				g.setAttribute("transform", transformString);
+			}
+		}
+		
+		if (target.model) {
+			var obj = target.model.get("transform");
+			if (obj) {
+				obj.scale = target.scale;
+				obj.height = target.parentNode.clientHeight;
+				obj.width = target.parentNode.clientWidth;
+				
+				obj.x1 = target.translateX;
+				obj.y1 = target.translateY;
+				obj.x2 = obj.x1 + obj.width*obj.scale;
+				obj.y2 = obj.y1 + obj.height*obj.scale;
+			}
+		}
+	}
+	
+	this.resetTransform = function(target) {
+		var settings = target.settings;
+		target.translateX = settings.x;
+		target.translateY = settings.y;
+		
+		if (settings.x < settings.x2) {
+			var factor = 0.90;
+			
+			// Reset scale and stuff.
+			var divHeight = target.parentNode.clientHeight;
+			var divWidth = target.parentNode.clientWidth;
+			
+			var width = settings.x2 - settings.x;
+			var height = settings.y2 - settings.y;
+			var aspectRatioGraph = height/width;
+			var aspectRatioDiv = divHeight/divWidth;
+			
+			var scale = aspectRatioGraph > aspectRatioDiv ? (divHeight/height)*factor : (divWidth/width)*factor;
+			target.scale = scale;
+		}
+		else {
+			target.zoomIndex = boundZoomLevel(target, settings.zoomIndex);
+			target.scale = target.zoomLevels[target.zoomIndex];
+		}
+	}
+	
+	this.animateTransform = function(target, scale, x, y, duration) {
+		var zoomLevel = calculateZoomLevel(scale, target.zoomLevels);
+		target.fromScaleLevel = target.zoomIndex;
+		target.toScaleLevel = zoomLevel;
+		target.fromX = target.translateX;
+		target.fromY = target.translateY;
+		target.fromScale = target.scale;
+		target.toScale = target.zoomLevels[zoomLevel];
+		target.toX = x;
+		target.toY = y;
+		target.startTime = new Date().getTime();
+		target.endTime = target.startTime + duration;
+		
+		this.animateTick(target);
+	}
+	
+	this.animateTick = function(target) {
+		var time = new Date().getTime();
+		if (time < target.endTime) {
+			var timeDiff = time - target.startTime;
+			var progress = timeDiff / (target.endTime - target.startTime);
+			
+			//target.zoomIndex = Math.floor((target.toScaleLevel - target.fromScaleLevel)*progress) + target.fromScaleLevel;
+			//target.scale = target.zoomLevels[target.zoomIndex];
+			target.scale = (target.toScale - target.fromScale)*progress + target.fromScale;
+			target.translateX = (target.toX - target.fromX)*progress + target.fromX;
+			target.translateY = (target.toY - target.fromY)*progress + target.fromY;
+			retransform(target);
+			setTimeout(function() {this.animateTick(target)}, 1);
+		}
+		else {
+			target.zoomIndex = target.toScaleLevel;
+			target.scale = target.zoomLevels[target.zoomIndex];
+			target.translateX = target.toX;
+			target.translateY = target.toY;
+			retransform(target);
+		}
+	}
+	
+	this.calculateZoomScale = function(scaleLevel, numLevels, points) {
+		if (scaleLevel <= 0) {
+			return points[0];
+		}
+		else if (scaleLevel >= numLevels) {
+			return points[points.length - 1];
+		}
+		var factor = (scaleLevel / numLevels) * (points.length - 1);
+		var floorIdx = Math.floor(factor);
+		var ceilingIdx = Math.ceil(factor);
+		
+		var b = factor - floorIdx;
+		
+		return b*(points[ceilingIdx] - points[floorIdx]) + points[floorIdx];
+	}
+	
+	this.calculateZoomLevel = function(scale, zoomLevels) {
+		if (scale >= zoomLevels[zoomLevels.length - 1]) {
+			return zoomLevels.length - 1;
+		}
+		else if (scale <= zoomLevels[0]) {
+			return 0;
+		}
+		
+		var i = 0;
+		// Plain old linear scan
+		for (; i < zoomLevels.length; ++i) {
+			if (scale < zoomLevels[i]) {
+				i--;
+				break;
+			}
+		}
+		
+		if (i < 0) {
+			return 0;
+		}
+		
+		return i;
+	}
+	
+	var methods = {	
+		init : function(options) {
+			var settings = {
+				x: 0,
+				y: 0,
+				x2: 0,
+				y2: 0,
+				minX: -1000,
+				minY: -1000,
+				maxX: 1000,
+				maxY: 1000,
+				zoomIndex: 24,
+				zoomPoints: [0.1, 0.14, 0.2, 0.4, 0.8, 1, 1.6, 2.4, 4, 8, 16],
+				zoomNumLevels: 48
+			};
+			if (options) {
+				$.extend(settings, options);
+			}
+			return this.each(function() {
+				var $this = $(this);
+				this.settings = settings;
+				this.marker = true;
+				
+				if (window.addEventListener) this.addEventListener('DOMMouseScroll', mouseScrolled, false);
+				this.onmousewheel = mouseScrolled;
+				this.onmousedown = mouseDown;
+				this.onmouseup = mouseUp;
+				this.onmousemove = mouseMove;
+				this.onmouseout = mouseOut;
+				
+				this.zoomLevels = new Array(settings.zoomNumLevels);
+				for (var i = 0; i < settings.zoomNumLevels; ++i) {
+					var scale = calculateZoomScale(i, settings.zoomNumLevels, settings.zoomPoints);
+					this.zoomLevels[i] = scale;
+				}
+				resetTransform(this);
+			});
+		},
+		transformToBox : function(arguments) {
+			var $this = $(this);
+			var target = ($this)[0];
+			var x = arguments.x;
+			var y = arguments.y;
+			var factor = 0.9;
+			var duration = arguments.duration;
+			
+			var width = arguments.width ? arguments.width : 1;
+			var height = arguments.height ? arguments.height : 1;
+			
+			var divHeight = target.parentNode.clientHeight;
+			var divWidth = target.parentNode.clientWidth;
+			
+			var aspectRatioGraph = height/width;
+			var aspectRatioDiv = divHeight/divWidth;
+
+			var scale = aspectRatioGraph > aspectRatioDiv ? (divHeight/height)*factor : (divWidth/width)*factor;
+			//console.log("(" + x + "," + y + "," + width.toPrecision(4) + "," + height.toPrecision(4) + ")");
+			//console.log("(rg:" + aspectRatioGraph.toPrecision(3) + ",rd:" + aspectRatioDiv.toPrecision(3) + "," + scale.toPrecision(3) + ")");
+			
+			if (arguments.maxScale) {
+				if (scale > arguments.maxScale) {
+					scale = arguments.maxScale;
+				}
+			}
+			if (arguments.minScale) {
+				if (scale < arguments.minScale) {
+					scale = arguments.minScale;
+				}
+			}
+			
+			// Center
+			var scaledWidth = width*scale;
+			var scaledHeight = height*scale;
+			
+			var sx = (divWidth - scaledWidth)/2 -scale*x;
+			var sy = (divHeight - scaledHeight)/2 -scale*y;
+			console.log("sx,sy:" + sx + "," + sy);
+			
+			if (duration != 0 && !duration) {
+				duration = 500;
+			}
+			
+			animateTransform(target, scale, sx, sy, duration);
+		},
+		attachNavigateModel : function(arguments) {
+			var $this = $(this);
+			var target = ($this)[0];
+			target.model = arguments;
+			
+			if (target.model) {
+				var obj = {};
+				obj.scale = target.scale;
+				obj.height = target.parentNode.clientHeight;
+				obj.width = target.parentNode.clientWidth;
+				
+				obj.x1 = target.translateX;
+				obj.y1 = target.translateY;
+				obj.x2 = obj.x1 + obj.height*obj.scale;
+				obj.y2 = obj.y1 + obj.width*obj.scale;
+				
+				target.model.set({transform : obj});
+			}
+		}
+	};
+	
+	// Main Constructor
+	$.fn.svgNavigate = function(method) {
+		if (methods[method]) {
+			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+		} else if (typeof method === 'object' || !method) {
+			return methods.init.apply(this, arguments);
+		} else {
+			$.error('Method ' + method + ' does not exist on svgNavigate');
+		}
+	};
+})(jQuery);