diff --git a/.gitignore b/.gitignore
index 496ee2c..83257ec 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-.DS_Store
\ No newline at end of file
+.DS_Store
+web-server.js
diff --git a/css/app.css b/css/app.css
index f1a072c..049c174 100644
--- a/css/app.css
+++ b/css/app.css
@@ -18,7 +18,7 @@ h1 {margin: 0;font-size: 1.5em;line-height: 42px;}
.pathList { border-right:1px solid rgb(220, 220, 224);position: fixed; top: 80px; bottom: 0; overflow: auto; z-index: 3;}
.envImages { position: fixed; top: 80px; bottom: 0; overflow: auto; left: 16.666%; z-index: 2;border-right: 1px solid rgb(220, 220, 224);}
.pathList a, .envImages a{text-align: center;display: block; border: 1px solid #ccc;max-height: 300px;overflow: hidden;}
-.pathList img, .envImages img{}
+.pathList img, .envImages img{position: relative;}
.diff-header {height: 37px;line-height: 35px;position: fixed;top: 43px;z-index: 5;width: 100%;background-color: rgb(229, 229, 235);}
.diff-header ul {margin: 0;padding: 0;margin-left: 2%}
.diff-header input {width: 95%;padding: 3px}
diff --git a/index.html b/index.html
index 2e1f6f2..d88ba4b 100644
--- a/index.html
+++ b/index.html
@@ -62,14 +62,14 @@
@@ -94,6 +96,7 @@
+
diff --git a/js/directives.js b/js/directives.js
new file mode 100644
index 0000000..35e3324
--- /dev/null
+++ b/js/directives.js
@@ -0,0 +1,50 @@
+angular.module('viffReport')
+ .directive('scrollToMapPosition', function ($window) {
+ return function (scope, element, attributes) {
+ var diffImageHeight, envImage, envImageHeightDiff;
+ var diffVisualHeight = $(window).height() - (43 + 35 + 60);
+ var originalSrc = element[0].src;
+
+ angular.element($window).bind("scroll", function () {
+ if (originalSrc !== element[0].src) {
+ envImage = {
+ height: element[0].height,
+ parentHeight: element[0].parentElement.clientHeight
+ };
+ diffImageHeight = $(".diffImage img").get(0).height;
+ envImageHeightDiff = envImage.height - envImage.parentHeight;
+ }
+
+ if (!envImage) {
+ return;
+ }
+
+ setBlankHeight(envImage);
+ var offsetY = $(document).scrollTop();
+ var offsetValue = parseInt(offsetY / diffImageHeight * envImage.height);
+
+ if (envImageHeightDiff < offsetValue) {
+ setElementTop(0 - envImageHeightDiff);
+ } else {
+ setElementTop(0 - offsetValue);
+ }
+
+ scope.$apply();
+ });
+
+ function setElementTop(imagePosition) {
+ if (typeof imagePosition === 'number') {
+ element[0].style.top = imagePosition + 'px';
+ } else {
+ element[0].style.top = imagePosition;
+ }
+ }
+
+ function setBlankHeight(envImage) {
+ var blankHeight = diffVisualHeight - (envImage.parentHeight * diffImageHeight / envImage.height);
+ if ($(".blank").height() < blankHeight) {
+ $(".blank").height(blankHeight);
+ }
+ }
+ }
+ });
diff --git a/js/filters.js b/js/filters.js
index a1394ca..ec96659 100644
--- a/js/filters.js
+++ b/js/filters.js
@@ -1,28 +1,28 @@
angular.module('viffReport')
.filter('imagePath', function () {
- return function(input){
- return encodeURIComponent(input);
- }
- }).filter('browserFilter', function (){
- return function(input){
- return "(" + input[0].toUpperCase() + ")" + input.slice(1);
- }
-}).filter('showCases', function(){
- return function(cases, showAll){
- var arrayToReturn = [];
- if(showAll){
- angular.forEach(cases, function(item){
- if (item.misMatchPercentage >= 0) {
- arrayToReturn.push(item);
- }
- });
- }else{
- angular.forEach(cases, function(item){
- if (item.misMatchPercentage > 0) {
- arrayToReturn.push(item);
- }
- });
- }
- return arrayToReturn;
+ return function (input) {
+ return encodeURIComponent(input);
+ }
+ }).filter('browserFilter', function () {
+ return function (input) {
+ return "(" + input[0].toUpperCase() + ")" + input.slice(1);
+ }
+ }).filter('showCases', function () {
+ return function (cases, showAll) {
+ var arrayToReturn = [];
+ if (showAll) {
+ angular.forEach(cases, function (item) {
+ if (item.misMatchPercentage >= 0) {
+ arrayToReturn.push(item);
+ }
+ });
+ } else {
+ angular.forEach(cases, function (item) {
+ if (item.misMatchPercentage > 0) {
+ arrayToReturn.push(item);
+ }
+ });
+ }
+ return arrayToReturn;
};
-});;
\ No newline at end of file
+ });;
diff --git a/report.json b/report.json
index 6c6abea..984451a 100644
--- a/report.json
+++ b/report.json
@@ -1 +1,50 @@
-{"compares":{"firefox":{"RCA home":{"images":{"firefox-build":"screenshots/firefox/RCA%20home/firefox-build.png","firefox-prod":"screenshots/firefox/RCA%20home/firefox-prod.png","diff":"screenshots/firefox/RCA%20home/diff.png"},"isSameDimensions":false,"misMatchPercentage":97.12,"analysisTime":1998},"RCA header":{"images":{"firefox-build":"screenshots/firefox/RCA%20header/firefox-build.png","firefox-prod":"screenshots/firefox/RCA%20header/firefox-prod.png","diff":"screenshots/firefox/RCA%20header/diff.png"},"isSameDimensions":true,"misMatchPercentage":4.66,"analysisTime":50},"RCA footer":{"images":{"firefox-build":"screenshots/firefox/RCA%20footer/firefox-build.png","firefox-prod":"screenshots/firefox/RCA%20footer/firefox-prod.png","diff":"screenshots/firefox/RCA%20footer/diff.png"},"isSameDimensions":true,"misMatchPercentage":0,"analysisTime":32}}},"caseCount":3,"sameCount":1,"diffCount":2,"totalAnalysisTime":2080}
\ No newline at end of file
+{
+ "compares": {
+ "firefox": {
+ "RCA home": {
+ "images": {
+ "firefox-build": "screenshots/firefox/RCA%20home/firefox-build.png",
+ "firefox-prod": "screenshots/firefox/RCA%20home/firefox-prod.png",
+ "diff": "screenshots/firefox/RCA%20home/diff.png"
+ },
+ "isSameDimensions": false,
+ "misMatchPercentage": 97.12,
+ "analysisTime": 1998
+ },
+ "RCA header": {
+ "images": {
+ "firefox-build": "screenshots/firefox/RCA%20header/firefox-build.png",
+ "firefox-prod": "screenshots/firefox/RCA%20header/firefox-prod.png",
+ "diff": "screenshots/firefox/RCA%20header/diff.png"
+ },
+ "isSameDimensions": true,
+ "misMatchPercentage": 4.66,
+ "analysisTime": 50
+ },
+ "RCA footer": {
+ "images": {
+ "firefox-build": "screenshots/firefox/RCA%20footer/firefox-build.png",
+ "firefox-prod": "screenshots/firefox/RCA%20footer/firefox-prod.png",
+ "diff": "screenshots/firefox/RCA%20footer/diff.png"
+ },
+ "isSameDimensions": true,
+ "misMatchPercentage": 0,
+ "analysisTime": 32
+ },
+ "Suncorp": {
+ "images": {
+ "firefox-build": "screenshots/firefox/Suncorp/firefox-build.png",
+ "firefox-prod": "screenshots/firefox/Suncorp/firefox-prod.png",
+ "diff": "screenshots/firefox/Suncorp/diff.png"
+ },
+ "isSameDimensions": true,
+ "misMatchPercentage": 90,
+ "analysisTime": 32
+ }
+ }
+ },
+ "caseCount": 4,
+ "sameCount": 1,
+ "diffCount": 3,
+ "totalAnalysisTime": 2080
+}
diff --git a/screenshots/firefox/Suncorp/diff.png b/screenshots/firefox/Suncorp/diff.png
new file mode 100644
index 0000000..85582be
Binary files /dev/null and b/screenshots/firefox/Suncorp/diff.png differ
diff --git a/screenshots/firefox/Suncorp/firefox-build.png b/screenshots/firefox/Suncorp/firefox-build.png
new file mode 100644
index 0000000..27641cd
Binary files /dev/null and b/screenshots/firefox/Suncorp/firefox-build.png differ
diff --git a/screenshots/firefox/Suncorp/firefox-prod.png b/screenshots/firefox/Suncorp/firefox-prod.png
new file mode 100644
index 0000000..920b4e6
Binary files /dev/null and b/screenshots/firefox/Suncorp/firefox-prod.png differ
diff --git a/web-server.js b/web-server.js
new file mode 100644
index 0000000..965bff9
--- /dev/null
+++ b/web-server.js
@@ -0,0 +1,245 @@
+#!/usr/bin/env node
+
+var util = require('util'),
+ http = require('http'),
+ fs = require('fs'),
+ url = require('url'),
+ events = require('events');
+
+var DEFAULT_PORT = 8000;
+
+function main(argv) {
+ new HttpServer({
+ 'GET': createServlet(StaticServlet),
+ 'HEAD': createServlet(StaticServlet)
+ }).start(Number(argv[2]) || DEFAULT_PORT);
+}
+
+function escapeHtml(value) {
+ return value.toString().
+ replace('<', '<').
+ replace('>', '>').
+ replace('"', '"');
+}
+
+function createServlet(Class) {
+ var servlet = new Class();
+ return servlet.handleRequest.bind(servlet);
+}
+
+/**
+ * An Http server implementation that uses a map of methods to decide
+ * action routing.
+ *
+ * @param {Object} Map of method => Handler function
+ */
+function HttpServer(handlers) {
+ this.handlers = handlers;
+ this.server = http.createServer(this.handleRequest_.bind(this));
+}
+
+HttpServer.prototype.start = function(port) {
+ this.port = port;
+ this.server.listen(port);
+ util.puts('Http Server running at http://localhost:' + port + '/');
+};
+
+HttpServer.prototype.parseUrl_ = function(urlString) {
+ var parsed = url.parse(urlString);
+ parsed.pathname = url.resolve('/', parsed.pathname);
+ return url.parse(url.format(parsed), true);
+};
+
+HttpServer.prototype.handleRequest_ = function(req, res) {
+ var logEntry = req.method + ' ' + req.url;
+ if (req.headers['user-agent']) {
+ logEntry += ' ' + req.headers['user-agent'];
+ }
+ util.puts(logEntry);
+ req.url = this.parseUrl_(req.url);
+ var handler = this.handlers[req.method];
+ if (!handler) {
+ res.writeHead(501);
+ res.end();
+ } else {
+ handler.call(this, req, res);
+ }
+};
+
+/**
+ * Handles static content.
+ */
+function StaticServlet() {}
+
+StaticServlet.MimeMap = {
+ 'txt': 'text/plain',
+ 'html': 'text/html',
+ 'css': 'text/css',
+ 'xml': 'application/xml',
+ 'json': 'application/json',
+ 'js': 'application/javascript',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'gif': 'image/gif',
+ 'png': 'image/png',
+ 'svg': 'image/svg+xml'
+};
+
+StaticServlet.prototype.handleRequest = function(req, res) {
+ var self = this;
+ var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
+ return String.fromCharCode(parseInt(hex, 16));
+ });
+ var parts = path.split('/');
+ if (parts[parts.length-1].charAt(0) === '.')
+ return self.sendForbidden_(req, res, path);
+ fs.stat(path, function(err, stat) {
+ if (err)
+ return self.sendMissing_(req, res, path);
+ if (stat.isDirectory())
+ //return self.sendDirectory_(req, res, path);
+ return self.sendFile_(req, res, "index.html");
+ return self.sendFile_(req, res, path);
+ });
+}
+
+StaticServlet.prototype.sendError_ = function(req, res, error) {
+ res.writeHead(500, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
Internal Server Error\n');
+ res.write('
Internal Server Error
');
+ res.write('
' + escapeHtml(util.inspect(error)) + '
');
+ util.puts('500 Internal Server Error');
+ util.puts(util.inspect(error));
+};
+
+StaticServlet.prototype.sendMissing_ = function(req, res, path) {
+ path = path.substring(1);
+ res.writeHead(404, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
404 Not Found\n');
+ res.write('
Not Found
');
+ res.write(
+ '
The requested URL ' +
+ escapeHtml(path) +
+ ' was not found on this server.
'
+ );
+ res.end();
+ util.puts('404 Not Found: ' + path);
+};
+
+StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
+ path = path.substring(1);
+ res.writeHead(403, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
403 Forbidden\n');
+ res.write('
Forbidden
');
+ res.write(
+ '
You do not have permission to access ' +
+ escapeHtml(path) + ' on this server.
'
+ );
+ res.end();
+ util.puts('403 Forbidden: ' + path);
+};
+
+StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
+ res.writeHead(301, {
+ 'Content-Type': 'text/html',
+ 'Location': redirectUrl
+ });
+ res.write('\n');
+ res.write('
301 Moved Permanently\n');
+ res.write('
Moved Permanently
');
+ res.write(
+ '
The document has moved here.
'
+ );
+ res.end();
+ util.puts('301 Moved Permanently: ' + redirectUrl);
+};
+
+StaticServlet.prototype.sendFile_ = function(req, res, path) {
+ var self = this;
+ var file = fs.createReadStream(path);
+ res.writeHead(200, {
+ 'Content-Type': StaticServlet.
+ MimeMap[path.split('.').pop()] || 'text/plain'
+ });
+ if (req.method === 'HEAD') {
+ res.end();
+ } else {
+ file.on('data', res.write.bind(res));
+ file.on('close', function() {
+ res.end();
+ });
+ file.on('error', function(error) {
+ self.sendError_(req, res, error);
+ });
+ }
+};
+
+StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
+ var self = this;
+ if (path.match(/[^\/]$/)) {
+ req.url.pathname += '/';
+ var redirectUrl = url.format(url.parse(url.format(req.url)));
+ return self.sendRedirect_(req, res, redirectUrl);
+ }
+ fs.readdir(path, function(err, files) {
+ if (err)
+ return self.sendError_(req, res, error);
+
+ if (!files.length)
+ return self.writeDirectoryIndex_(req, res, path, []);
+
+ var remaining = files.length;
+ files.forEach(function(fileName, index) {
+ fs.stat(path + '/' + fileName, function(err, stat) {
+ if (err)
+ return self.sendError_(req, res, err);
+ if (stat.isDirectory()) {
+ files[index] = fileName + '/';
+ }
+ if (!(--remaining))
+ return self.writeDirectoryIndex_(req, res, path, files);
+ });
+ });
+ });
+};
+
+StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
+ path = path.substring(1);
+ res.writeHead(200, {
+ 'Content-Type': 'text/html'
+ });
+ if (req.method === 'HEAD') {
+ res.end();
+ return;
+ }
+ res.write('\n');
+ res.write('
' + escapeHtml(path) + '\n');
+ res.write('\n');
+ res.write('
Directory: ' + escapeHtml(path) + '
');
+ res.write('
');
+ files.forEach(function(fileName) {
+ if (fileName.charAt(0) !== '.') {
+ res.write('- ' +
+ escapeHtml(fileName) + '
');
+ }
+ });
+ res.write('
');
+ res.end();
+};
+
+// Must be last,
+main(process.argv);