1 var MAX_SWAP_IMG_SIZE = 400; 2 3 angular.module('diff_viewer', []). 4 config(['$routeProvider', function($routeProvider) { 5 // Show the list of differences by default 6 $routeProvider. 7 otherwise({ templateUrl: '/diff_list.html', controller: DiffListController}); 8 }]). 9 directive('swapImg', function() { 10 // Custom directive for showing an image that gets swapped my mouseover. 11 return { 12 restrict: 'E', // The directive can be used as an element name 13 replace: true, // The directive replaces itself with the template 14 template: '<canvas ng-mouseenter="swap()" ng-mouseleave="swap()"></canvas>', 15 scope: { // The attributes below are bound to the scope 16 leftSrc: '@', 17 rightSrc: '@', 18 side: '@' 19 }, 20 link: function(scope, elm, attrs, ctrl) { 21 var leftImage = new Image(); 22 var rightImage = new Image(); 23 var ctx = elm[0].getContext('2d'); 24 25 scope.render = function() { 26 var image; 27 if (scope.side == "left") { 28 image = leftImage; 29 } else { 30 image = rightImage; 31 } 32 33 // Make it so the maximum size of an image is MAX_SWAP_IMG_SIZE, and the images are 34 // scaled down in halves. 35 var divisor = 1; 36 while ((image.width / divisor) > MAX_SWAP_IMG_SIZE) { 37 divisor *= 2; 38 } 39 40 // Set canvas to correct size and draw the image into it 41 elm[0].width = image.width / divisor; 42 elm[0].height = image.height / divisor; 43 ctx.drawImage(image, 0, 0, elm[0].width, elm[0].height); 44 }; 45 46 // When the leftSrc attribute changes, load the image and then rerender 47 attrs.$observe('leftSrc', function(value) { 48 leftImage.src = value; 49 leftImage.onload = function() { 50 if (scope.side == "left") { 51 scope.render(); 52 } 53 }; 54 }); 55 56 // When the rightSrc attribute changes, load the image and then rerender 57 attrs.$observe('rightSrc', function(value) { 58 rightImage.src = value; 59 rightImage.onload = function() { 60 if (scope.side == "right") { 61 scope.render(); 62 } 63 }; 64 }); 65 66 // Swap which side to draw onto the canvas and then rerender 67 scope.swap = function() { 68 if (scope.side == "left") { 69 scope.side = "right"; 70 } else { 71 scope.side = "left"; 72 } 73 scope.render(); 74 }; 75 } 76 }; 77 }); 78 79 function DiffListController($scope, $http, $location, $timeout, $parse) { 80 // Detect if we are running the web server version of the viewer. If so, we set a flag and 81 // enable some extra functionality of the website for rebaselining. 82 $scope.isDynamic = ($location.protocol() == "http" || $location.protocol() == "https"); 83 84 // Label each kind of differ for the sort buttons. 85 $scope.differs = [ 86 { 87 "title": "Different Pixels" 88 }, 89 { 90 "title": "Perceptual Difference" 91 } 92 ]; 93 94 // Puts the records within AngularJS scope 95 $scope.records = SkPDiffRecords.records; 96 97 // Keep track of the index of the last record to change so that shift clicking knows what range 98 // of records to apply the action to. 99 $scope.lastSelectedIndex = undefined; 100 101 // Indicates which diff metric is used for sorting 102 $scope.sortIndex = 1; 103 104 // Called by the sort buttons to adjust the metric used for sorting 105 $scope.setSortIndex = function(idx) { 106 $scope.sortIndex = idx; 107 108 // Because the index of things has most likely changed, the ranges of shift clicking no 109 // longer make sense from the user's point of view. We reset it to avoid confusion. 110 $scope.lastSelectedIndex = undefined; 111 }; 112 113 // A predicate for pulling out the number used for sorting 114 $scope.sortingDiffer = function(record) { 115 return record.diffs[$scope.sortIndex].result; 116 }; 117 118 // Flash status indicator on the page, and then remove it so the style can potentially be 119 // reapplied later. 120 $scope.flashStatus = function(success) { 121 var flashStyle = success ? "success-flash" : "failure-flash"; 122 var flashDurationMillis = success ? 500 : 800; 123 124 // Store the style in the record. The row will pick up the style this way instead of through 125 // index because index can change with sort order. 126 $scope.statusClass = flashStyle; 127 128 // The animation cannot be repeated unless the class is removed the element. 129 $timeout(function() { 130 $scope.statusClass = ""; 131 }, flashDurationMillis); 132 }; 133 134 $scope.selectedRebaseline = function(index, event) { 135 // Retrieve the records in the same order they are displayed. 136 var recordsInOrder = $parse("records | orderBy:sortingDiffer")($scope); 137 138 // If the user is shift clicking, apply the last tick/untick to all elements in between this 139 // record, and the last one they ticked/unticked. 140 if (event.shiftKey && $scope.lastSelectedIndex !== undefined) { 141 var currentAction = recordsInOrder[index].isRebaselined; 142 var smallerIndex = Math.min($scope.lastSelectedIndex, index); 143 var largerIndex = Math.max($scope.lastSelectedIndex, index); 144 for (var recordIndex = smallerIndex; recordIndex <= largerIndex; recordIndex++) { 145 recordsInOrder[recordIndex].isRebaselined = currentAction; 146 } 147 $scope.lastSelectedIndex = index; 148 } 149 else 150 { 151 $scope.lastSelectedIndex = index; 152 } 153 154 }; 155 156 $scope.commitRebaselines = function() { 157 // Gather up all records that have the rebaseline set. 158 var rebaselines = []; 159 for (var recordIndex = 0; recordIndex < $scope.records.length; recordIndex++) { 160 if ($scope.records[recordIndex].isRebaselined) { 161 rebaselines.push($scope.records[recordIndex].testPath); 162 } 163 } 164 $http.post("/commit_rebaselines", { 165 "rebaselines": rebaselines 166 }).success(function(data) { 167 $scope.flashStatus(data.success); 168 }).error(function() { 169 $scope.flashStatus(false); 170 }); 171 }; 172 } 173