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 @@
- +
- +
@@ -78,6 +78,8 @@ +
+
@@ -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('
  1. ' + + escapeHtml(fileName) + '
  2. '); + } + }); + res.write('
'); + res.end(); +}; + +// Must be last, +main(process.argv);