From 75a907eeabb2983bda2bd1081eacb8a7d94db63b Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 24 Nov 2014 09:34:46 -0500
Subject: [PATCH] Add .bat and .cmd to the pretty print extensions

---
 src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js |  157 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 136 insertions(+), 21 deletions(-)

diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
index 27cca8c..6157513 100644
--- a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
+++ b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
@@ -22,7 +22,7 @@
  *
  * The styling of the slider is to be done in CSS. Currently recognized options:
  * - initial: <float> clipped to [0..1], default 0
- * - handleClass: <string> to assign to the handle div element created.
+ * - handleClass: <string> to assign to the handle span element created.
  * If no handleClass is specified, a very plain default style is assigned.
  */
 function rangeSlider(elem, options) {
@@ -30,7 +30,7 @@
 	options.initial = Math.min(1.0, Math.max(0, options.initial));
 	
 	var $elem = $(elem);
-	var $handle = $('<div></div>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
+	var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
 	var $root = $(document.documentElement);
 	var $doc = $(document);	
 	var lastRatio = options.initial;
@@ -38,8 +38,8 @@
 	/** Mousemove event handler to track the mouse and move the slider. Generates slider:pos events. */
 	function track(e) {
 		var pos = $elem.offset().left;
-		var width = $elem.width();
-		var handleWidth = $handle.width();
+		var width = $elem.innerWidth();
+		var handleWidth = $handle.outerWidth(false);
 		var range = width - handleWidth;
 		if (range <= 0) return;
 		var delta = Math.min(range, Math.max (0, e.pageX - pos - handleWidth / 2));
@@ -57,44 +57,64 @@
 
     /** Snaps the slider to the given ratio and generates a slider:pos event with the new ratio. */
 	function setTo(ratio) {
-		var w = $elem.width();
+		var w = $elem.innerWidth();
 		if (w <= 0 || $elem.is(':hidden')) return;
 		lastRatio = Math.min( 1.0, Math.max(0, ratio));
-		$handle.css('left', "" + Math.max(0, 100 * (lastRatio * (w - $handle.width())) / w) + '%');
+		$handle.css('left', "" + Math.max(0, 100 * (lastRatio * (w - $handle.outerWidth(false))) / w) + '%');
 		$elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] });
 	}
 	
 	/**
 	 * Moves the slider to the given ratio, clipped to [0..1], in duration milliseconds.
-	 * Generates slider:pos events during the animation. If duration === 0, same as setTo.
-	 * Default duration is 500ms.
+	 * Generates slider:pos events during the animation. If duration <= 30, same as setTo.
+	 * Default duration is 500ms. If a callback is given, it's called once the animation
+	 * has completed.
 	 */
-	function moveTo(ratio, duration) {
+	function moveTo(ratio, duration, callback) {
 		ratio = Math.min(1.0, Math.max(0, ratio));
-		if (ratio === lastRatio) return;
+		if (ratio === lastRatio) {
+			if (typeof callback == 'function') callback();
+			return;
+		}
 		if (typeof duration == 'undefined') duration = 500;
-		if (duration === 0) {
+		if (duration <= 30) {
+			 // Cinema is 24 or 48 frames/sec, so 20-40ms per frame. Makes no sense to animate for such a short duration.
 			setTo(ratio);
+			if (typeof callback == 'function') callback();
 		} else {
-			var target = ratio * ($elem.width() - $handle.width());
+			var target = ratio * ($elem.innerWidth() - $handle.outerWidth(false));
 			if (ratio > lastRatio) target--; else target++;
 			$handle.stop().animate({left: target},
 				{ 'duration' : duration,
 				  'step' : function() {
-						lastRatio = Math.min(1.0, Math.max(0, $handle.position().left / ($elem.width() - $handle.width())));
+						lastRatio = Math.min(1.0, Math.max(0, $handle.position().left / ($elem.innerWidth() - $handle.outerWidth(false))));
 						$elem.trigger('slider:pos', { ratio : lastRatio, handle : $handle[0] });
 					},
-				  'complete' : function() { setTo(ratio); } // Last step gives us a % value again.
+				  'complete' : function() { setTo(ratio); if (typeof callback == 'function') callback(); } // Ensure we have again a % value
 				}
 			);
 		}
 	}
 	
+	/**
+	 * As moveTo, but determines an appropriate duration in the range [0..maxDuration] on its own,
+	 * depending on the distance the handle would move. If no maxDuration is given it defaults
+	 * to 1500ms.
+	 */
+	function moveAuto(ratio, maxDuration, callback) {
+		if (typeof maxDuration == 'undefined') maxDuration = 1500;
+		var delta = ratio - lastRatio;
+		if (delta < 0) delta = -delta;
+		var speed = $elem.innerWidth() * delta * 2;
+		if (speed > maxDuration) speed = maxDuration;
+		moveTo(ratio, speed, callback);
+	}
+
 	/** Returns the current ratio. */
 	function getValue() {
 		return lastRatio;
 	}
-		
+
 	$elem.append($handle);
 	if (options.handleClass) {
 		$handle.addClass(options.handleClass);
@@ -108,9 +128,11 @@
 		$root.addClass('no-select');
 		$doc.on('mousemove', track);
 		$doc.on('mouseup', end);
+		e.stopPropagation();
+		e.preventDefault();
 	});
 
-	return { setRatio: setTo, moveRatio: moveTo, getRatio: getValue, handle: $handle[0] };
+	return { setRatio: setTo, moveRatio: moveTo, 'moveAuto': moveAuto, getRatio: getValue, handle: $handle[0] };
 }
 
 function setup() {
@@ -119,14 +141,15 @@
 		var $overlaySlider = $this.find('.imgdiff-ovr-slider').first();
 		var $opacitySlider = $this.find('.imgdiff-opa-slider').first();
 		var overlayAccess = rangeSlider($overlaySlider, {handleClass: 'imgdiff-ovr-handle'});
-		rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'});
+		var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'});
 		var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity
 		var $div = $img.parent(); // This controls visibility: here we change width.
+		var blinking = false;
 		
 		$overlaySlider.on('slider:pos', function(e, data) {
 			var pos = $(data.handle).offset().left;
 			var imgLeft = $img.offset().left; // Global
-			var imgW = $img.outerWidth();
+			var imgW = $img.outerWidth(true);
 			var imgOff = $img.position().left; // From left edge of $div
 			if (pos <= imgLeft) {
 				$div.width(0);
@@ -136,10 +159,102 @@
 				$div.width(imgW + imgOff);
 			}
 		});
-		$opacitySlider.on('slider:pos', function(e, data) {
-			if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500); // Make old image visible in a nice way
-			$img.css('opacity', 1.0 - data.ratio);
+		$overlaySlider.css('cursor', 'pointer');
+		$overlaySlider.on('mousedown', function(e) {
+			var newRatio = (e.pageX - $overlaySlider.offset().left) / $overlaySlider.innerWidth();
+			var oldRatio = overlayAccess.getRatio();
+			if (newRatio !== oldRatio) {
+				overlayAccess.moveAuto(newRatio);
+			}
 		});
+		
+		var autoShowing = false;
+		$opacitySlider.on('slider:pos', function(e, data) {
+			if ($div.width() <= 0 && !blinking) {
+				// Make old image visible in a nice way, *then* adjust opacity
+				autoShowing = true;
+				overlayAccess.moveAuto(1.0, 500, function() {
+					$img.stop().animate(
+						{opacity: 1.0 - opacityAccess.getRatio()},
+						{duration: 400,
+						 complete: function () {
+							// In case the opacity handle was moved while we were trying to catch up
+							$img.css('opacity', 1.0 - opacityAccess.getRatio());
+							autoShowing = false;
+						 }
+						}
+					);
+				});
+			} else if (!autoShowing) {
+				$img.css('opacity', 1.0 - data.ratio);
+			}
+		});
+		$opacitySlider.on('click', function(e) {
+			var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth();
+			var oldRatio = opacityAccess.getRatio();
+			if (newRatio !== oldRatio) {
+				if ($div.width() <= 0) {
+					overlayAccess.moveRatio(1.0, 500, function() {opacityAccess.moveAuto(newRatio);}); // Make old image visible in a nice way
+				} else {
+					opacityAccess.moveAuto(newRatio)
+				}
+			}
+			e.preventDefault();
+		});
+			
+		// Blinking before and after images is a good way for the human eye to catch differences.
+		var $blinker = $this.find('.imgdiff-blink');
+		var initialOpacity = null;
+		$blinker.on('click', function(e) {
+			if (blinking) {
+				window.clearTimeout(blinking);
+				$blinker.children('img').first().css('border', '1px solid transparent');
+				opacityAccess.setRatio(initialOpacity);
+				blinking = null;
+			} else {
+				$blinker.children('img').first().css('border', '1px solid #AAA');
+				initialOpacity = opacityAccess.getRatio();
+				var currentOpacity = 1.0;
+				function blink() {
+					opacityAccess.setRatio(currentOpacity);
+					currentOpacity = 1.0 - currentOpacity;
+					// Keep frequeny below 2Hz (i.e., delay above 500ms)
+					blinking = window.setTimeout(blink, 600);
+				}
+				if ($div.width() <= 0) {
+					overlayAccess.moveRatio(1.0, 500, blink);
+				} else {
+					blink();
+				}
+			}
+			e.preventDefault();
+		});
+		
+		// Subtracting before and after images is another good way to detect differences. Result will be
+		// black where identical.		
+		if (typeof $img[0].style.mixBlendMode != 'undefined') {
+			// Feature test: does the browser support the mix-blend-mode CSS property from the Compositing 
+			// and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )?
+			// As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to
+			// make do with the blink comparator only.
+			var $sub = $this.find('.imgdiff-subtract');
+			$sub.css('display', 'inline-block');
+			$sub.on('click', function (e) {
+				var curr = $img.css('mix-blend-mode');
+				if (curr != 'difference') {
+					curr = 'difference';
+					$sub.children('img').first().css('border', '1px solid #AAA');
+					if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500);
+					opacityAccess.setRatio(0);
+				} else {
+					curr = 'normal';
+					$sub.children('img').first().css('border', '1px solid transparent');
+					
+				}
+				$img.css('mix-blend-mode', curr);
+				e.preventDefault();
+			});
+		}
 	});
 }
 

--
Gitblit v1.9.1