From bc0d51656ac1444fcb4a55ea2354847fa92cf81b Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Feb 2014 17:28:20 -0500 Subject: [PATCH 1/7] Add ability to see a build's build pack, including browsing and downloading of the contents if it is a zip --- endpoints/api.py | 34 +- initdb.py | 4 +- static/css/quay.css | 28 +- static/js/app.js | 1 + static/js/controllers.js | 124 ++- static/js/graphing.js | 1304 +++++++++++++++------------- static/lib/jszip.min.js | 14 + static/partials/build-package.html | 45 + static/partials/repo-build.html | 6 + templates/index.html | 3 +- test/data/test.db | Bin 142336 -> 393216 bytes test/test_api_usage.py | 10 +- 12 files changed, 936 insertions(+), 637 deletions(-) create mode 100755 static/lib/jszip.min.js create mode 100644 static/partials/build-package.html diff --git a/endpoints/api.py b/endpoints/api.py index 472064dc2..39f86b816 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -1149,7 +1149,7 @@ def get_repo(namespace, repository): abort(403) # Permission denied -def build_status_view(build_obj): +def build_status_view(build_obj, can_write=False): status = build_logs.get_status(build_obj.uuid) return { 'id': build_obj.uuid, @@ -1157,7 +1157,8 @@ def build_status_view(build_obj): 'started': build_obj.started, 'display_name': build_obj.display_name, 'status': status, - 'resource_key': build_obj.resource_key + 'resource_key': build_obj.resource_key if can_write else None, + 'is_writer': can_write } @@ -1167,9 +1168,10 @@ def get_repo_builds(namespace, repository): permission = ReadRepositoryPermission(namespace, repository) is_public = model.repository_is_public(namespace, repository) if permission.can() or is_public: + can_write = ModifyRepositoryPermission(namespace, repository).can() builds = model.list_repository_builds(namespace, repository) return jsonify({ - 'builds': [build_status_view(build) for build in builds] + 'builds': [build_status_view(build, can_write) for build in builds] }) abort(403) # Permission denied @@ -1183,7 +1185,29 @@ def get_repo_build_status(namespace, repository, build_uuid): is_public = model.repository_is_public(namespace, repository) if permission.can() or is_public: build = model.get_repository_build(namespace, repository, build_uuid) - return jsonify(build_status_view(build)) + if not build: + abort(404) + + can_write = ModifyRepositoryPermission(namespace, repository).can() + return jsonify(build_status_view(build, can_write)) + + abort(403) # Permission denied + + +@api.route('/repository//build//archiveurl', + methods=['GET']) +@parse_repository_name +def get_repo_build_archive_url(namespace, repository, build_uuid): + permission = ModifyRepositoryPermission(namespace, repository) + if permission.can(): + build = model.get_repository_build(namespace, repository, build_uuid) + if not build: + abort(404) + + url = user_files.get_file_url(build.resource_key) + return jsonify({ + 'url': url + }) abort(403) # Permission denied @@ -1249,7 +1273,7 @@ def request_repo_build(namespace, repository): {'repo': repository, 'namespace': namespace, 'fileid': dockerfile_id}, repo=repo) - resp = jsonify(build_status_view(build_request)) + resp = jsonify(build_status_view(build_request, True)) repo_string = '%s/%s' % (namespace, repository) resp.headers['Location'] = url_for('api.get_repo_build_status', repository=repo_string, diff --git a/initdb.py b/initdb.py index cb29d5246..fb710bb58 100644 --- a/initdb.py +++ b/initdb.py @@ -277,8 +277,8 @@ def populate_database(): token = model.create_access_token(building, 'write') tag = 'ci.devtable.com:5000/%s/%s' % (building.namespace, building.name) - build = model.create_repository_build(building, token, '123-45-6789', tag, - 'build-name') + build = model.create_repository_build(building, token, '701dcc3724fb4f2ea6c31400528343cd', + tag, 'build-name') build.uuid = 'deadbeef-dead-beef-dead-beefdeadbeef' build.save() diff --git a/static/css/quay.css b/static/css/quay.css index fa9994afd..73a0121e7 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -2263,23 +2263,10 @@ p.editable:hover i { overflow: hidden; } -#changes-tree-container .node rect { - cursor: pointer; - fill: #fff; - fill-opacity: 1; - stroke: #fff; - stroke-width: 1.5px; -} - #changes-tree-container .node .change-icon { font-size: 14px; } -#changes-tree-container .node text { - font: 12px sans-serif; - pointer-events: none; -} - #changes-tree-container .node.changed text { fill: rgb(73, 100, 209); } @@ -2293,7 +2280,20 @@ p.editable:hover i { fill: rgb(209, 73, 73); } -#changes-tree-container path.link { +.file-tree-base .node rect { + cursor: pointer; + fill: #fff; + fill-opacity: 1; + stroke: #fff; + stroke-width: 1.5px; +} + +.file-tree-base .node text { + font: 12px sans-serif; + pointer-events: none; +} + +.file-tree-base path.link { fill: none; stroke: #9ecae1; stroke-width: 1.5px; diff --git a/static/js/app.js b/static/js/app.js index e2e90d22e..359212e95 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -791,6 +791,7 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}). when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}). when('/repository/:namespace/:name/build', {templateUrl: '/static/partials/repo-build.html', controller:RepoBuildCtrl, reloadOnSearch: false}). + when('/repository/:namespace/:name/build/:buildid/buildpack', {templateUrl: '/static/partials/build-package.html', controller:BuildPackageCtrl, reloadOnSearch: false}). when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list', templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}). when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html', diff --git a/static/js/controllers.js b/static/js/controllers.js index d3ab397c1..13a9a672d 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -774,6 +774,128 @@ function RepoCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiServi loadViewInfo(); } +function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $timeout) { + var namespace = $routeParams.namespace; + var name = $routeParams.name; + var buildid = $routeParams.buildid; + + var params = { + 'repository': namespace + '/' + name, + 'build_uuid': buildid + }; + + $scope.initializeTree = function() { + if ($scope.drawn) { return; } + + $scope.drawn = true; + $timeout(function() { + $scope.tree.draw('file-tree-container'); + }, 10); + }; + + var processBuildPack = function(response) { + // Try to load as a zip file. + var zipFiles = null; + var zip = null; + try { + var zip = new JSZip(response); + zipFiles = zip.files; + } catch (e) { + } + + // Find the Dockerfile in the zip file. If there isn't any zip file, then the response + // itself (should) be the Dockerfile. + if (zipFiles && Object.keys(zipFiles).length) { + // Load the dockerfile contents. + var dockerfile = zip.file('Dockerfile'); + if (dockerfile) { + $scope.dockerFileContents = dockerfile.asText(); + } + + // Build the zip file tree. + $scope.tree = new FileTree(Object.keys(zipFiles)); + $($scope.tree).bind('fileClicked', function(e) { + var file = zip.file(e.path); + if (file) { + var blob = new Blob([file.asArrayBuffer()]); + saveAs(blob, file.name); + } + }); + } else { + $scope.dockerFileContents = response; + } + + $scope.loaded = true; + }; + + var downloadBuildPack = function() { + $scope.downloadProgress = 0; + $scope.downloading = true; + + ApiService.getRepoBuildArchiveUrl(null, params).then(function(resp) { + startDownload(resp['url']); + }, function() { + $scope.downloading = false; + $scope.downloadError = true; + }); + }; + + var startDownload = function(url) { + var request = new XMLHttpRequest(); + request.open('GET', url, true); + if (request.overrideMimeType) { + request.overrideMimeType('text/plain; charset=x-user-defined'); + } + request.onprogress = function(e) { + $scope.$apply(function() { + var percentLoaded; + if (e.lengthComputable) { + $scope.downloadProgress = (e.loaded / e.total) * 100; + } + }); + }; + request.onerror = function() { + $scope.$apply(function() { + $scope.downloading = false; + $scope.downloadError = true; + }); + }; + request.onreadystatechange = function() { + var state = request.readyState; + if (state == 4) { + $scope.$apply(function() { + $scope.downloading = false; + processBuildPack(request.responseText); + }); + return; + } + }; + request.send(); + }; + + var getBuildInfo = function() { + $scope.repository_build = ApiService.getRepoBuildStatus(null, params, true).then(function(resp) { + if (!resp['is_writer']) { + $rootScope.title = 'Unknown build'; + $scope.accessDenied = true; + return; + } + + $rootScope.title = 'Repository Build Pack - ' + resp['display_name']; + $scope.repobuild = resp; + $scope.repo = { + 'namespace': namespace, + 'name': name + }; + + downloadBuildPack(); + return resp; + }); + }; + + getBuildInfo(); +} + function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) { var namespace = $routeParams.namespace; var name = $routeParams.name; @@ -805,7 +927,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope }; $scope.adjustLogHeight = function() { - $('.build-logs').height($(window).height() - 385); + $('.build-logs').height($(window).height() - 415); }; $scope.askRestartBuild = function(build) { diff --git a/static/js/graphing.js b/static/js/graphing.js index f0a3aa486..d73844ceb 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -1,29 +1,29 @@ /** * Bind polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility */ -if (!Function.prototype.bind) { - Function.prototype.bind = function (oThis) { - if (typeof this !== "function") { - // closest thing possible to the ECMAScript 5 internal IsCallable function - throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); - } + if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } - var aArgs = Array.prototype.slice.call(arguments, 1), - fToBind = this, - fNOP = function () {}, - fBound = function () { - return fToBind.apply(this instanceof fNOP && oThis - ? this - : oThis, - aArgs.concat(Array.prototype.slice.call(arguments))); - }; + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; - fNOP.prototype = this.prototype; - fBound.prototype = new fNOP(); + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); - return fBound; - }; -} + return fBound; + }; + } var DEPTH_HEIGHT = 100; var DEPTH_WIDTH = 140; @@ -32,55 +32,55 @@ var DEPTH_WIDTH = 140; * Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock) */ function ImageHistoryTree(namespace, name, images, formatComment, formatTime, formatCommand) { - /** - * The namespace of the repo. - */ - this.repoNamespace_ = namespace; + /** + * The namespace of the repo. + */ + this.repoNamespace_ = namespace; - /** - * The name of the repo. - */ - this.repoName_ = name; + /** + * The name of the repo. + */ + this.repoName_ = name; - /** - * The images to display. - */ - this.images_ = images; + /** + * The images to display. + */ + this.images_ = images; - /** - * Method to invoke to format a comment for an image. - */ - this.formatComment_ = formatComment; + /** + * Method to invoke to format a comment for an image. + */ + this.formatComment_ = formatComment; - /** - * Method to invoke to format the time for an image. - */ - this.formatTime_ = formatTime; + /** + * Method to invoke to format the time for an image. + */ + this.formatTime_ = formatTime; - /** - * Method to invoke to format the command for an image. - */ - this.formatCommand_ = formatCommand; + /** + * Method to invoke to format the command for an image. + */ + this.formatCommand_ = formatCommand; - /** - * The current tag (if any). - */ - this.currentTag_ = null; + /** + * The current tag (if any). + */ + this.currentTag_ = null; - /** - * The current image (if any). - */ - this.currentImage_ = null; + /** + * The current image (if any). + */ + this.currentImage_ = null; - /** - * The currently highlighted node (if any). - */ - this.currentNode_ = null; + /** + * The currently highlighted node (if any). + */ + this.currentNode_ = null; - /** - * Counter for creating unique IDs. - */ - this.idCounter_ = 0; + /** + * Counter for creating unique IDs. + */ + this.idCounter_ = 0; } @@ -88,21 +88,21 @@ function ImageHistoryTree(namespace, name, images, formatComment, formatTime, fo * Calculates the dimensions of the tree. */ ImageHistoryTree.prototype.calculateDimensions_ = function(container) { - var cw = Math.max(document.getElementById(container).clientWidth, this.maxWidth_ * DEPTH_WIDTH); - var ch = this.maxHeight_ * (DEPTH_HEIGHT + 10); + var cw = Math.max(document.getElementById(container).clientWidth, this.maxWidth_ * DEPTH_WIDTH); + var ch = this.maxHeight_ * (DEPTH_HEIGHT + 10); - var margin = { top: 40, right: 20, bottom: 20, left: 80 }; - var m = [margin.top, margin.right, margin.bottom, margin.left]; - var w = cw - m[1] - m[3]; - var h = ch - m[0] - m[2]; + var margin = { top: 40, right: 20, bottom: 20, left: 80 }; + var m = [margin.top, margin.right, margin.bottom, margin.left]; + var w = cw - m[1] - m[3]; + var h = ch - m[0] - m[2]; - return { - 'w': w, - 'h': h, - 'm': m, - 'cw': cw, - 'ch': ch - }; + return { + 'w': w, + 'h': h, + 'm': m, + 'cw': cw, + 'ch': ch + }; }; @@ -129,40 +129,40 @@ ImageHistoryTree.prototype.setupOverscroll_ = function() { * Updates the dimensions of the tree. */ ImageHistoryTree.prototype.updateDimensions_ = function() { - var container = this.container_; - var dimensions = this.calculateDimensions_(container); + var container = this.container_; + var dimensions = this.calculateDimensions_(container); - var m = dimensions.m; - var w = dimensions.w; - var h = dimensions.h; - var cw = dimensions.cw; - var ch = dimensions.ch; + var m = dimensions.m; + var w = dimensions.w; + var h = dimensions.h; + var cw = dimensions.cw; + var ch = dimensions.ch; - // Set the height of the container so that it never goes offscreen. - $('#' + container).removeOverscroll(); - var viewportHeight = $(window).height(); - var boundingBox = document.getElementById(container).getBoundingClientRect(); - document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 150) + 'px'; + // Set the height of the container so that it never goes offscreen. + $('#' + container).removeOverscroll(); + var viewportHeight = $(window).height(); + var boundingBox = document.getElementById(container).getBoundingClientRect(); + document.getElementById(container).style.maxHeight = (viewportHeight - boundingBox.top - 150) + 'px'; - this.setupOverscroll_(); - - // Update the tree. - var rootSvg = this.rootSvg_; - var tree = this.tree_; - var vis = this.vis_; + this.setupOverscroll_(); + + // Update the tree. + var rootSvg = this.rootSvg_; + var tree = this.tree_; + var vis = this.vis_; - var ow = w + m[1] + m[3]; - var oh = h + m[0] + m[2]; - rootSvg - .attr("width", ow) - .attr("height", oh) - .attr("style", "width: " + ow + "px; height: " + oh + "px"); + var ow = w + m[1] + m[3]; + var oh = h + m[0] + m[2]; + rootSvg + .attr("width", ow) + .attr("height", oh) + .attr("style", "width: " + ow + "px; height: " + oh + "px"); - tree.size([w, h]); - vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")"); + tree.size([w, h]); + vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")"); - return dimensions; + return dimensions; }; @@ -170,80 +170,80 @@ ImageHistoryTree.prototype.updateDimensions_ = function() { * Draws the tree. */ ImageHistoryTree.prototype.draw = function(container) { - // Build the root of the tree. - var result = this.buildRoot_(); - this.maxWidth_ = result['maxWidth']; - this.maxHeight_ = result['maxHeight']; + // Build the root of the tree. + var result = this.buildRoot_(); + this.maxWidth_ = result['maxWidth']; + this.maxHeight_ = result['maxHeight']; - // Save the container. - this.container_ = container; - - // Create the tree and all its components. - var tree = d3.layout.tree() - .separation(function() { return 2; }); + // Save the container. + this.container_ = container; + + // Create the tree and all its components. + var tree = d3.layout.tree() + .separation(function() { return 2; }); - var diagonal = d3.svg.diagonal() - .projection(function(d) { return [d.x, d.y]; }); + var diagonal = d3.svg.diagonal() + .projection(function(d) { return [d.x, d.y]; }); - var rootSvg = d3.select("#" + container).append("svg:svg") - .attr("class", "image-tree"); + var rootSvg = d3.select("#" + container).append("svg:svg") + .attr("class", "image-tree"); - var vis = rootSvg.append("svg:g"); + var vis = rootSvg.append("svg:g"); - var formatComment = this.formatComment_; - var formatTime = this.formatTime_; - var formatCommand = this.formatCommand_; + var formatComment = this.formatComment_; + var formatTime = this.formatTime_; + var formatCommand = this.formatCommand_; - var tip = d3.tip() - .attr('class', 'd3-tip') - .offset([-1, 24]) - .direction('e') - .html(function(d) { - var html = ''; - if (d.virtual) { - return d.name; - } + var tip = d3.tip() + .attr('class', 'd3-tip') + .offset([-1, 24]) + .direction('e') + .html(function(d) { + var html = ''; + if (d.virtual) { + return d.name; + } - if (d.collapsed) { - for (var i = 1; i < d.encountered.length; ++i) { - html += '' + d.encountered[i].image.id.substr(0, 12) + ''; - html += '' + formatTime(d.encountered[i].image.created) + ''; - } - return html; - } + if (d.collapsed) { + for (var i = 1; i < d.encountered.length; ++i) { + html += '' + d.encountered[i].image.id.substr(0, 12) + ''; + html += '' + formatTime(d.encountered[i].image.created) + ''; + } + return html; + } - if (!d.image) { - return '(This repository is empty)'; - } + if (!d.image) { + return '(This repository is empty)'; + } - if (d.image.comment) { - html += '' + formatComment(d.image.comment) + ''; - } - if (d.image.command && d.image.command.length) { - html += '' + formatCommand(d.image) + ''; - } - html += '' + formatTime(d.image.created) + ''; - return html; - }) + if (d.image.comment) { + html += '' + formatComment(d.image.comment) + ''; + } + if (d.image.command && d.image.command.length) { + html += '' + formatCommand(d.image) + ''; + } + html += '' + formatTime(d.image.created) + ''; + return html; + }) - vis.call(tip); + vis.call(tip); - // Save all the state created. - this.diagonal_ = diagonal; - this.vis_ = vis; - this.rootSvg_ = rootSvg; - this.tip_ = tip; - this.tree_ = tree; + // Save all the state created. + this.diagonal_ = diagonal; + this.vis_ = vis; + this.rootSvg_ = rootSvg; + this.tip_ = tip; + this.tree_ = tree; - // Update the dimensions of the tree. - var dimensions = this.updateDimensions_(); + // Update the dimensions of the tree. + var dimensions = this.updateDimensions_(); - // Populate the tree. - this.root_.x0 = dimensions.cw / 2; - this.root_.y0 = 0; + // Populate the tree. + this.root_.x0 = dimensions.cw / 2; + this.root_.y0 = 0; - this.setTag_(this.currentTag_); - this.setupOverscroll_(); + this.setTag_(this.currentTag_); + this.setupOverscroll_(); }; @@ -310,8 +310,8 @@ ImageHistoryTree.prototype.getAncestors_ = function(image) { */ ImageHistoryTree.prototype.changeTag_ = function(tagName) { $(this).trigger({ - 'type': 'tagChanged', - 'tag': tagName + 'type': 'tagChanged', + 'tag': tagName }); this.setTag_(tagName); }; @@ -323,8 +323,8 @@ ImageHistoryTree.prototype.changeTag_ = function(tagName) { */ ImageHistoryTree.prototype.changeImage_ = function(imageId) { $(this).trigger({ - 'type': 'imageChanged', - 'image': this.findImage_(function(image) { return image.id == imageId; }) + 'type': 'imageChanged', + 'image': this.findImage_(function(image) { return image.id == imageId; }) }); this.setImage_(imageId); }; @@ -334,83 +334,83 @@ ImageHistoryTree.prototype.changeImage_ = function(imageId) { * Builds the root node for the tree. */ ImageHistoryTree.prototype.buildRoot_ = function() { - // Build the formatted JSON block for the tree. It must be of the form: - // { - // "name": "...", - // "children": [...] - // } - var formatted = {"name": "No images found"}; + // Build the formatted JSON block for the tree. It must be of the form: + // { + // "name": "...", + // "children": [...] + // } + var formatted = {"name": "No images found"}; - // Build a node for each image. - var imageByDBID = {}; - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - var imageNode = { - "name": image.id.substr(0, 12), - "children": [], - "image": image, - "tags": image.tags - }; - imageByDBID[image.dbid] = imageNode; - } - this.imageByDBID_ = imageByDBID; - - // For each node, attach it to its immediate parent. If there is no immediate parent, - // then the node is the root. - var roots = []; - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - var imageNode = imageByDBID[image.dbid]; - var ancestors = this.getAncestors_(image); - var immediateParent = ancestors[ancestors.length - 1] * 1; - var parent = imageByDBID[immediateParent]; - if (parent) { - // Add a reference to the parent. This makes walking the tree later easier. - imageNode.parent = parent; - parent.children.push(imageNode); - } else { - roots.push(imageNode); - } - } - - // If there are multiple root nodes, then there is at least one branch without shared - // ancestry and we use the virtual node. Otherwise, we use the root node found. - var root = { - 'name': '', - 'children': roots, - 'virtual': true + // Build a node for each image. + var imageByDBID = {}; + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + var imageNode = { + "name": image.id.substr(0, 12), + "children": [], + "image": image, + "tags": image.tags }; + imageByDBID[image.dbid] = imageNode; + } + this.imageByDBID_ = imageByDBID; - if (roots.length == 1) { - root = roots[0]; + // For each node, attach it to its immediate parent. If there is no immediate parent, + // then the node is the root. + var roots = []; + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + var imageNode = imageByDBID[image.dbid]; + var ancestors = this.getAncestors_(image); + var immediateParent = ancestors[ancestors.length - 1] * 1; + var parent = imageByDBID[immediateParent]; + if (parent) { + // Add a reference to the parent. This makes walking the tree later easier. + imageNode.parent = parent; + parent.children.push(imageNode); + } else { + roots.push(imageNode); } + } - // Determine the maximum number of nodes at a particular level. This is used to size - // the width of the tree properly. - var maxChildCount = roots.length; - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - var imageNode = imageByDBID[image.dbid]; - maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode)); - } + // If there are multiple root nodes, then there is at least one branch without shared + // ancestry and we use the virtual node. Otherwise, we use the root node found. + var root = { + 'name': '', + 'children': roots, + 'virtual': true + }; - // Compact the graph so that any single chain of three (or more) images becomes a collapsed - // section. We only do this if the max width is > 1 (since for a single width tree, no long - // chain will hide a branch). - if (maxChildCount > 1) { - this.collapseNodes_(root); - } + if (roots.length == 1) { + root = roots[0]; + } - // Determine the maximum height of the tree. - var maxHeight = this.determineMaximumHeight_(root); + // Determine the maximum number of nodes at a particular level. This is used to size + // the width of the tree properly. + var maxChildCount = roots.length; + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + var imageNode = imageByDBID[image.dbid]; + maxChildCount = Math.max(maxChildCount, this.determineMaximumChildCount_(imageNode)); + } - // Finally, set the root node and return. - this.root_ = root; + // Compact the graph so that any single chain of three (or more) images becomes a collapsed + // section. We only do this if the max width is > 1 (since for a single width tree, no long + // chain will hide a branch). + if (maxChildCount > 1) { + this.collapseNodes_(root); + } - return { - 'maxWidth': maxChildCount + 1, - 'maxHeight': maxHeight - }; + // Determine the maximum height of the tree. + var maxHeight = this.determineMaximumHeight_(root); + + // Finally, set the root node and return. + this.root_ = root; + + return { + 'maxWidth': maxChildCount + 1, + 'maxHeight': maxHeight + }; }; @@ -419,13 +419,13 @@ ImageHistoryTree.prototype.buildRoot_ = function() { * compact. */ ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) { - var maxHeight = 0; - if (node.children) { - for (var i = 0; i < node.children.length; ++i) { - maxHeight = Math.max(this.determineMaximumHeight_(node.children[i]), maxHeight); - } - } - return maxHeight + 1; + var maxHeight = 0; + if (node.children) { + for (var i = 0; i < node.children.length; ++i) { + maxHeight = Math.max(this.determineMaximumHeight_(node.children[i]), maxHeight); + } + } + return maxHeight + 1; }; @@ -434,37 +434,37 @@ ImageHistoryTree.prototype.determineMaximumHeight_ = function(node) { * compact. */ ImageHistoryTree.prototype.collapseNodes_ = function(node) { - if (node.children.length == 1) { - // Keep searching downward until we find a node with more than a single child. - var current = node; - var previous = node; - var encountered = []; - while (current.children.length == 1) { - encountered.push(current); - previous = current; - current = current.children[0]; - } - - if (encountered.length >= 3) { - // Collapse the node. - var collapsed = { - "name": '(' + (encountered.length - 1) + ' images)', - "children": [current], - "collapsed": true, - "encountered": encountered - }; - node.children = [collapsed]; - - // Update the parent relationships. - collapsed.parent = node; - current.parent = collapsed; - return; - } + if (node.children.length == 1) { + // Keep searching downward until we find a node with more than a single child. + var current = node; + var previous = node; + var encountered = []; + while (current.children.length == 1) { + encountered.push(current); + previous = current; + current = current.children[0]; } - for (var i = 0; i < node.children.length; ++i) { - this.collapseNodes_(node.children[i]); + if (encountered.length >= 3) { + // Collapse the node. + var collapsed = { + "name": '(' + (encountered.length - 1) + ' images)', + "children": [current], + "collapsed": true, + "encountered": encountered + }; + node.children = [collapsed]; + + // Update the parent relationships. + collapsed.parent = node; + current.parent = collapsed; + return; } + } + + for (var i = 0; i < node.children.length; ++i) { + this.collapseNodes_(node.children[i]); + } }; @@ -472,15 +472,15 @@ ImageHistoryTree.prototype.collapseNodes_ = function(node) { * Determines the maximum child count for the node and its children. */ ImageHistoryTree.prototype.determineMaximumChildCount_ = function(node) { - var children = node.children; - var myLevelCount = children.length; - var nestedCount = 0; + var children = node.children; + var myLevelCount = children.length; + var nestedCount = 0; - for (var i = 0; i < children.length; ++i) { - nestedCount += children[i].children.length; - } + for (var i = 0; i < children.length; ++i) { + nestedCount += children[i].children.length; + } - return Math.max(myLevelCount, nestedCount); + return Math.max(myLevelCount, nestedCount); }; @@ -489,14 +489,14 @@ ImageHistoryTree.prototype.determineMaximumChildCount_ = function(node) { * if none. */ ImageHistoryTree.prototype.findImage_ = function(checker) { - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - if (checker(image)) { - return image; - } + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + if (checker(image)) { + return image; } + } - return null; + return null; }; @@ -504,12 +504,12 @@ ImageHistoryTree.prototype.findImage_ = function(checker) { * Marks the full node path from the given starting node on whether it is highlighted. */ ImageHistoryTree.prototype.markPath_ = function(startingNode, isHighlighted) { - var currentNode = startingNode; - currentNode.current = isHighlighted; - while (currentNode != null) { - currentNode.highlighted = isHighlighted; - currentNode = currentNode.parent; - } + var currentNode = startingNode; + currentNode.current = isHighlighted; + while (currentNode != null) { + currentNode.highlighted = isHighlighted; + currentNode = currentNode.parent; + } }; @@ -517,51 +517,51 @@ ImageHistoryTree.prototype.markPath_ = function(startingNode, isHighlighted) { * Sets the current tag displayed in the tree. */ ImageHistoryTree.prototype.setTag_ = function(tagName) { - if (tagName == this.currentTag_) { - return; - } + if (tagName == this.currentTag_) { + return; + } - var imageByDBID = this.imageByDBID_; - - // Save the current tag. - var previousTagName = this.currentTag_; - this.currentTag_ = tagName; - this.currentImage_ = null; - - // Update the path. - var tagImage = this.findImage_(function(image) { - return image.tags.indexOf(tagName || '(no tag specified)') >= 0; - }); + var imageByDBID = this.imageByDBID_; + + // Save the current tag. + var previousTagName = this.currentTag_; + this.currentTag_ = tagName; + this.currentImage_ = null; + + // Update the path. + var tagImage = this.findImage_(function(image) { + return image.tags.indexOf(tagName || '(no tag specified)') >= 0; + }); - if (tagImage) { - this.setHighlightedPath_(tagImage); - } + if (tagImage) { + this.setHighlightedPath_(tagImage); + } - // Ensure that the children are in the correct order. - for (var i = 0; i < this.images_.length; ++i) { - var image = this.images_[i]; - var imageNode = this.imageByDBID_[image.dbid]; - var ancestors = this.getAncestors_(image); - var immediateParent = ancestors[ancestors.length - 1] * 1; - var parent = imageByDBID[immediateParent]; - if (parent && imageNode.highlighted) { - var arr = parent.children; - if (parent._children) { - arr = parent._children; - } + // Ensure that the children are in the correct order. + for (var i = 0; i < this.images_.length; ++i) { + var image = this.images_[i]; + var imageNode = this.imageByDBID_[image.dbid]; + var ancestors = this.getAncestors_(image); + var immediateParent = ancestors[ancestors.length - 1] * 1; + var parent = imageByDBID[immediateParent]; + if (parent && imageNode.highlighted) { + var arr = parent.children; + if (parent._children) { + arr = parent._children; + } - if (arr[0] != imageNode) { - var index = arr.indexOf(imageNode); - if (index > 0) { - arr.splice(index, 1); - arr.splice(0, 0, imageNode); - } - } + if (arr[0] != imageNode) { + var index = arr.indexOf(imageNode); + if (index > 0) { + arr.splice(index, 1); + arr.splice(0, 0, imageNode); } + } } + } - // Update the tree. - this.update_(this.root_); + // Update the tree. + this.update_(this.root_); }; @@ -569,19 +569,19 @@ ImageHistoryTree.prototype.setTag_ = function(tagName) { * Sets the current image highlighted in the tree. */ ImageHistoryTree.prototype.setImage_ = function(imageId) { - // Find the new current image. - var newImage = this.findImage_(function(image) { - return image.id == imageId; - }); + // Find the new current image. + var newImage = this.findImage_(function(image) { + return image.id == imageId; + }); - if (newImage == this.currentImage_) { - return; - } + if (newImage == this.currentImage_) { + return; + } - this.setHighlightedPath_(newImage); - this.currentImage_ = newImage; - this.currentTag_ = null; - this.update_(this.root_); + this.setHighlightedPath_(newImage); + this.currentImage_ = newImage; + this.currentTag_ = null; + this.update_(this.root_); }; @@ -611,42 +611,42 @@ ImageHistoryTree.prototype.update_ = function(source) { // Update the nodes... var node = vis.selectAll("g.node") - .data(nodes, function(d) { return d.id || (d.id = that.idCounter_++); }); + .data(nodes, function(d) { return d.id || (d.id = that.idCounter_++); }); // Enter any new nodes at the parent's previous position. var nodeEnter = node.enter().append("svg:g") - .attr("class", "node") - .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; }); + .attr("class", "node") + .attr("transform", function(d) { return "translate(" + source.x0 + "," + source.y0 + ")"; }); nodeEnter.append("svg:circle") - .attr("r", 1e-6) - .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }) - .on("click", function(d) { that.toggle_(d); that.update_(d); }); - + .attr("r", 1e-6) + .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; }) + .on("click", function(d) { that.toggle_(d); that.update_(d); }); + // Create the group that will contain the node name and its tags. var g = nodeEnter.append("svg:g").style("fill-opacity", 1e-6); // Add the repo ID. g.append("svg:text") - .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) - .attr("dy", ".35em") - .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) - .text(function(d) { return d.name; }) - .on("click", function(d) { if (d.image) { that.changeImage_(d.image.id); } }) - .on('mouseover', tip.show) - .on('mouseout', tip.hide); + .attr("x", function(d) { return d.children || d._children ? -10 : 10; }) + .attr("dy", ".35em") + .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; }) + .text(function(d) { return d.name; }) + .on("click", function(d) { if (d.image) { that.changeImage_(d.image.id); } }) + .on('mouseover', tip.show) + .on('mouseout', tip.hide); - nodeEnter.selectAll("tags") - .append("svg:text") - .text("bar"); + nodeEnter.selectAll("tags") + .append("svg:text") + .text("bar"); // Create the foreign object to hold the tags (if any). var fo = g.append("svg:foreignObject") - .attr("class", "fo") - .attr("x", 14) - .attr("y", 12) - .attr("width", 110) - .attr("height", DEPTH_HEIGHT - 20); + .attr("class", "fo") + .attr("x", 14) + .attr("y", 12) + .attr("width", 110) + .attr("height", DEPTH_HEIGHT - 20); // Add the tags container. fo.append('xhtml:div') @@ -660,81 +660,81 @@ ImageHistoryTree.prototype.update_ = function(source) { // Transition nodes to their new position. var nodeUpdate = node.transition() - .duration(duration) - .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); + .duration(duration) + .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); // Update the node circle. nodeUpdate.select("circle") - .attr("r", 4.5) - .attr("class", function(d) { - return (d._children ? "closed " : "open ") + (d.current ? "current " : "") + (d.highlighted ? "highlighted " : ""); - }) - .style("fill", function(d) { - if (d.current) { - return ""; - } - return d._children ? "lightsteelblue" : "#fff"; - }); + .attr("r", 4.5) + .attr("class", function(d) { + return (d._children ? "closed " : "open ") + (d.current ? "current " : "") + (d.highlighted ? "highlighted " : ""); + }) + .style("fill", function(d) { + if (d.current) { + return ""; + } + return d._children ? "lightsteelblue" : "#fff"; + }); // Update the repo text. nodeUpdate.select("text") - .attr("class", function(d) { - if (d.collapsed) { - return 'collapsed'; - } - if (d.virtual) { - return 'virtual'; - } - if (!currentImage) { + .attr("class", function(d) { + if (d.collapsed) { + return 'collapsed'; + } + if (d.virtual) { + return 'virtual'; + } + if (!currentImage) { return ''; - } - return d.image.id == currentImage.id ? 'current' : ''; - }); + } + return d.image.id == currentImage.id ? 'current' : ''; + }); // Ensure that the node is visible. nodeUpdate.select("g") - .style("fill-opacity", 1); + .style("fill-opacity", 1); // Update the tags. node.select(".tags") - .html(function(d) { - if (!d.tags) { - return ''; - } + .html(function(d) { + if (!d.tags) { + return ''; + } - var html = ''; - for (var i = 0; i < d.tags.length; ++i) { - var tag = d.tags[i]; - var kind = 'default'; - if (tag == currentTag) { - kind = 'success'; - } - html += '' + tag + ''; - } - return html; - }); + var html = ''; + for (var i = 0; i < d.tags.length; ++i) { + var tag = d.tags[i]; + var kind = 'default'; + if (tag == currentTag) { + kind = 'success'; + } + html += '' + tag + ''; + } + return html; + }); // Listen for click events on the labels. node.selectAll(".tag") - .on("click", function(d, e) { - var tag = this.getAttribute('data-tag'); - if (tag) { - that.changeTag_(tag); - } - }) - .on("contextmenu", function(d, e) { - d3.event.preventDefault(); + .on("click", function(d, e) { + var tag = this.getAttribute('data-tag'); + if (tag) { + that.changeTag_(tag); + } + }) + .on("contextmenu", function(d, e) { + d3.event.preventDefault(); - var tag = this.getAttribute('data-tag'); - if (tag) { - $(that).trigger({ - 'type': 'showTagMenu', - 'tag': tag, - 'clientX': d3.event.clientX, - 'clientY': d3.event.clientY - }); - } - }); + var tag = this.getAttribute('data-tag'); + if (tag) { + $(that).trigger({ + 'type': 'showTagMenu', + 'tag': tag, + 'clientX': d3.event.clientX, + 'clientY': d3.event.clientY + }); + } + }); // Ensure the tags are visible. nodeUpdate.select(".tags") @@ -744,59 +744,59 @@ ImageHistoryTree.prototype.update_ = function(source) { // we force a redraw by adjusting the height of the object ever so slightly. nodeUpdate.select(".fo") .attr('height', function(d) { - return DEPTH_HEIGHT - 20 + Math.random() / 10; + return DEPTH_HEIGHT - 20 + Math.random() / 10; }); // Transition exiting nodes to the parent's new position. var nodeExit = node.exit().transition() - .duration(duration) - .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; }) - .remove(); + .duration(duration) + .attr("transform", function(d) { return "translate(" + source.x + "," + source.y + ")"; }) + .remove(); nodeExit.select("circle") - .attr("r", 1e-6); + .attr("r", 1e-6); nodeExit.select(".tags") - .style("display", "none"); + .style("display", "none"); nodeExit.select("g") - .style("fill-opacity", 1e-6); + .style("fill-opacity", 1e-6); // Update the links... var link = vis.selectAll("path.link") - .data(tree.links(nodes), function(d) { return d.target.id; }); + .data(tree.links(nodes), function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("svg:path", "g") - .attr("class", function(d) { - var isHighlighted = d.target.highlighted; - return "link " + (isHighlighted ? "highlighted": ""); - }) - .attr("d", function(d) { - var o = {x: source.x0, y: source.y0}; - return diagonal({source: o, target: o}); - }) + .attr("class", function(d) { + var isHighlighted = d.target.highlighted; + return "link " + (isHighlighted ? "highlighted": ""); + }) + .attr("d", function(d) { + var o = {x: source.x0, y: source.y0}; + return diagonal({source: o, target: o}); + }) .transition() - .duration(duration) - .attr("d", diagonal); + .duration(duration) + .attr("d", diagonal); // Transition links to their new position. link.transition() - .duration(duration) - .attr("d", diagonal) - .attr("class", function(d) { - var isHighlighted = d.target.highlighted; - return "link " + (isHighlighted ? "highlighted": ""); - }); + .duration(duration) + .attr("d", diagonal) + .attr("class", function(d) { + var isHighlighted = d.target.highlighted; + return "link " + (isHighlighted ? "highlighted": ""); + }); // Transition exiting nodes to the parent's new position. link.exit().transition() - .duration(duration) - .attr("d", function(d) { - var o = {x: source.x, y: source.y}; - return diagonal({source: o, target: o}); - }) - .remove(); + .duration(duration) + .attr("d", function(d) { + var o = {x: source.x, y: source.y}; + return diagonal({source: o, target: o}); + }) + .remove(); // Stash the old positions for transition. nodes.forEach(function(d) { @@ -834,90 +834,80 @@ ImageHistoryTree.prototype.dispose = function() { /** * Based off of http://bl.ocks.org/mbostock/1093025 by Mike Bostock (@mbostock) */ -function ImageFileChangeTree(image, changes) { - /** - * The parent image. - */ - this.image_ = image; - - /** - * The changes being drawn. - */ - this.changes_ = changes; - - /** - * Counter for creating unique IDs. - */ - this.idCounter_ = 0; - - /** - * Map from file path to associated tree node. - */ - this.nodeMap_ = {}; +function FileTreeBase() { + /** + * Counter for creating unique IDs. + */ + this.idCounter_ = 0; + + /** + * Map from file path to associated tree node. + */ + this.nodeMap_ = {}; } /** * Calculates the dimensions of the tree. */ -ImageFileChangeTree.prototype.calculateDimensions_ = function(container) { - var cw = document.getElementById(container).clientWidth; - var barHeight = 20; - var ch = (this.changes_.length * barHeight) + 40; +FileTreeBase.prototype.calculateDimensions_ = function(container) { + var cw = document.getElementById(container).clientWidth; + var barHeight = 20; + var ch = (this.getNodesHeight() * barHeight) + 40; - var margin = { top: 40, right: 00, bottom: 20, left: 20 }; - var m = [margin.top, margin.right, margin.bottom, margin.left]; - var w = cw - m[1] - m[3]; - var h = ch - m[0] - m[2]; + var margin = { top: 40, right: 00, bottom: 20, left: 20 }; + var m = [margin.top, margin.right, margin.bottom, margin.left]; + var w = cw - m[1] - m[3]; + var h = ch - m[0] - m[2]; - var barWidth = cw * 0.8 - m[1] - m[3]; + var barWidth = cw * 0.8 - m[1] - m[3]; - return { - 'w': w, - 'h': h, - 'm': m, - 'cw': cw, - 'ch': ch, - 'bw': barWidth, - 'bh': barHeight - }; + return { + 'w': w, + 'h': h, + 'm': m, + 'cw': cw, + 'ch': ch, + 'bw': barWidth, + 'bh': barHeight + }; }; /** * Updates the dimensions of the tree. */ -ImageFileChangeTree.prototype.updateDimensions_ = function() { - var container = this.container_; - var dimensions = this.calculateDimensions_(container); +FileTreeBase.prototype.updateDimensions_ = function() { + var container = this.container_; + var dimensions = this.calculateDimensions_(container); - var w = dimensions.w; - var h = dimensions.h; - var m = dimensions.m; + var w = dimensions.w; + var h = dimensions.h; + var m = dimensions.m; - // Update the tree. - var rootSvg = this.rootSvg_; - var tree = this.tree_; - var vis = this.vis_; + // Update the tree. + var rootSvg = this.rootSvg_; + var tree = this.tree_; + var vis = this.vis_; - rootSvg - .attr("width", w + m[1] + m[3]) - .attr("height", h + m[0] + m[2]); + rootSvg + .attr("width", w + m[1] + m[3]) + .attr("height", h + m[0] + m[2]); - tree.size([h, 100]); - vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")"); + tree.size([h, 100]); + vis.attr("transform", "translate(" + m[3] + "," + m[0] + ")"); - this.barWidth_ = dimensions.bw; - this.barHeight_ = dimensions.bh; + this.barWidth_ = dimensions.bw; + this.barHeight_ = dimensions.bh; - return dimensions; + return dimensions; }; /** * Redraws the image change tree to fit the new size. */ -ImageFileChangeTree.prototype.notifyResized = function() { +FileTreeBase.prototype.notifyResized = function() { this.updateDimensions_(); this.update_(this.root_); }; @@ -926,7 +916,7 @@ ImageFileChangeTree.prototype.notifyResized = function() { /** * Disposes of the tree. */ -ImageFileChangeTree.prototype.dispose = function() { +FileTreeBase.prototype.dispose = function() { var container = this.container_ ; document.getElementById(container).innerHTML = ''; }; @@ -935,9 +925,9 @@ ImageFileChangeTree.prototype.dispose = function() { /** * Draws the tree. */ -ImageFileChangeTree.prototype.draw = function(container) { +FileTreeBase.prototype.draw = function(container) { this.container_ = container; - + var dimensions = this.calculateDimensions_(container); var w = dimensions.w; @@ -954,12 +944,13 @@ ImageFileChangeTree.prototype.draw = function(container) { .projection(function(d) { return [d.y, d.x]; }); var rootSvg = d3.select("#" + container).append("svg:svg") + .attr('class', 'file-tree-base') .attr("width", w) .attr("height", h); var vis = rootSvg - .append("svg:g") - .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); + .append("svg:g") + .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); this.rootSvg_ = rootSvg; this.tree_ = tree; @@ -973,14 +964,9 @@ ImageFileChangeTree.prototype.draw = function(container) { /** * Populates the tree and then draws it. */ -ImageFileChangeTree.prototype.populateAndDraw_ = function() { - // For each change, generate all the node(s) needed for the folders, as well - // as the final node (if any) for the file. - for (var i = 0; i < this.changes_.length; ++i) { - var filepath = this.changes_[i].file; - var node = this.buildNodes_(filepath); - node.kind = this.changes_[i].kind; - } +FileTreeBase.prototype.populateAndDraw_ = function() { + // Build all the nodes for the file tree. + this.buildAllNodes(); // Sort the children of each node so that folders are on top. var sortByName = function (a, b) { @@ -1023,7 +1009,7 @@ ImageFileChangeTree.prototype.populateAndDraw_ = function() { /** * Builds all the nodes in the tree. */ -ImageFileChangeTree.prototype.buildNodes_ = function(path) { +FileTreeBase.prototype.buildNodes_ = function(path) { var parts = path.split('/'); for (var i = 0; i < parts.length; ++i) { var currentPath = parts.slice(0, i + 1).join('/'); @@ -1032,7 +1018,8 @@ ImageFileChangeTree.prototype.buildNodes_ = function(path) { if (currentPath.length > 0) { var parentPath = parts.slice(0, i).join('/'); - this.nodeMap_[parentPath]._children.push(this.nodeMap_[currentPath]); + var parent = this.buildNodes_(parentPath); + parent._children.push(this.nodeMap_[currentPath]); } } } @@ -1043,7 +1030,7 @@ ImageFileChangeTree.prototype.buildNodes_ = function(path) { /** * Calculates the count of visible nodes. */ -ImageFileChangeTree.prototype.getVisibleCount_ = function(node) { +FileTreeBase.prototype.getVisibleCount_ = function(node) { if (node.children) { var count = 1; for (var i = 0; i < node.children.length; ++i) { @@ -1058,7 +1045,7 @@ ImageFileChangeTree.prototype.getVisibleCount_ = function(node) { /** * Calculates the height for the container. */ -ImageFileChangeTree.prototype.getContainerHeight_ = function() { +FileTreeBase.prototype.getContainerHeight_ = function() { var dimensions = this.calculateDimensions_(this.container_); var barHeight = this.barHeight_; var height = (this.getVisibleCount_(this.root_) * (barHeight + 2)); @@ -1069,7 +1056,7 @@ ImageFileChangeTree.prototype.getContainerHeight_ = function() { /** * Updates the tree starting at the given source node. */ -ImageFileChangeTree.prototype.update_ = function(source) { +FileTreeBase.prototype.update_ = function(source) { var that = this; var tree = this.tree_; var vis = this.vis_; @@ -1081,10 +1068,10 @@ ImageFileChangeTree.prototype.update_ = function(source) { var duration = 400; var color = function(d) { - if (d.kind) { - return ''; - } - return d._children ? "#E9E9E9" : "#c6dbef"; + if (d.kind) { + return ''; + } + return d._children ? "#E9E9E9" : "#c6dbef"; }; // Update the height of the container and the SVG. @@ -1101,120 +1088,124 @@ ImageFileChangeTree.prototype.update_ = function(source) { // Update the nodes... var node = vis.selectAll("g.node") - .data(nodes, function(d) { return d.id || (d.id = that.idCounter_++); }); + .data(nodes, function(d) { return d.id || (d.id = that.idCounter_++); }); var nodeEnter = node.enter().append("svg:g") - .attr("class", function(d) { - return "node " + (d.kind ? d.kind : 'folder'); - }) - .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) - .style("opacity", 1e-6); + .attr("class", function(d) { + return "node " + (d.kind ? d.kind : 'folder'); + }) + .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) + .style("opacity", 1e-6); // Enter any new nodes at the parent's previous position. nodeEnter.append("svg:rect") - .attr("class", "main-rect") - .attr("y", -barHeight / 2) - .attr("height", barHeight) - .attr("width", barWidth) - .style("fill", color) - .on("click", function(d) { that.toggle_(d); that.update_(source); }); - + .attr("class", "main-rect") + .attr("y", -barHeight / 2) + .attr("height", barHeight) + .attr("width", barWidth) + .style("fill", color) + .on("click", function(d) { + if (d.kind) { + $(that).trigger({ + 'type': 'fileClicked', + 'path': d.path + }); + return; + } + that.toggle_(d); + that.update_(source); + }); + nodeEnter.append("svg:text") - .attr("dy", 3.5) - .attr("dx", 5.5 + 18) - .text(function(d) { return d.name; }); + .attr("dy", 3.5) + .attr("dx", 5.5 + 18) + .text(function(d) { return d.name; }); var body = nodeEnter.append('svg:foreignObject') - .attr("class", "fo") - .attr("width", 18) - .attr("height", barHeight) - .append('xhtml:body'); + .attr("class", "fo") + .attr("width", 18) + .attr("height", barHeight) + .append('xhtml:body'); body.append('div') - .attr('class', 'node-icon'); + .attr('class', 'node-icon'); // Transition nodes to their new position. nodeEnter.transition() - .duration(duration) - .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) - .style("opacity", 1); - + .duration(duration) + .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) + .style("opacity", 1); + node.transition() - .duration(duration) - // TODO: reenable for full animation - //.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) - .style("opacity", 1) + .duration(duration) + // TODO: reenable for full animation + //.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) + .style("opacity", 1) .select("rect") - .style("fill", color); + .style("fill", color); // TODO: remove if full animation. node.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); node.select('.main-rect') - .attr("y", -barHeight / 2) - .attr("height", barHeight) - .attr("width", barWidth) + .attr("y", -barHeight / 2) + .attr("height", barHeight) + .attr("width", barWidth); node.select('.fo') - .attr("x", function(d) { return d.kind ? barWidth - 18 : 0; }) - .attr("y", -10) + .attr("x", function(d) { return d.kind ? barWidth - 18 : 0; }) + .attr("y", -10) node.select('.node-icon') - .html(function(d) { - if (!d.kind) { - var folder = d._children ? 'fa fa-folder' : 'fa fa-folder-open'; - return ''; - } + .html(function(d) { + if (!d.kind) { + var folder = d._children ? 'fa fa-folder' : 'fa fa-folder-open'; + return ''; + } - var icon = { - 'added': 'plus-square', - 'removed': 'minus-square', - 'changed': 'pencil-square' - }; - - return ''; - }); + return that.determineIcon(d); + }); // Transition exiting nodes to the parent's new position. node.exit().transition() - .duration(duration) - // TODO: reenable for full animation - // .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) - .style("opacity", 1e-6) - .remove(); + .duration(duration) + // TODO: reenable for full animation + // .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) + .style("opacity", 1e-6) + .remove(); // Update the links... var link = vis.selectAll("path.link") - .data(tree.links(nodes), function(d) { return d.target.id; }); + .data(tree.links(nodes), function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("svg:path", "g") - .attr("class", "link") - .attr("d", function(d) { - var o = {x: source.x0, y: source.y0}; - return diagonal({source: o, target: o}); - }) + .attr("class", "link") + .attr("d", function(d) { + var o = {x: source.x0, y: source.y0}; + return diagonal({source: o, target: o}); + }) .transition() - .duration(duration) - .attr("d", diagonal); + .duration(duration) + .attr("d", diagonal); // Transition links to their new position. link.transition() - .duration(duration) - .attr("d", function(d) { - var s = {x: d.source.x + 14, y: d.source.y + 9}; - var t = d.target; - return diagonal({source: s, target: t}); - }); + .duration(duration) + .attr("d", function(d) { + var s = {x: d.source.x + 14, y: d.source.y + 9}; + var t = d.target; + return diagonal({source: s, target: t}); + }); // Transition exiting nodes to the parent's new position. link.exit().transition() - .duration(duration) - .attr("d", function(d) { - var o = {x: source.x, y: source.y}; - return diagonal({source: o, target: o}); - }) - .remove(); + .duration(duration) + .attr("d", function(d) { + var o = {x: source.x, y: source.y}; + return diagonal({source: o, target: o}); + }) + .remove(); // Stash the old positions for transition. nodes.forEach(function(d) { @@ -1226,7 +1217,7 @@ ImageFileChangeTree.prototype.update_ = function(source) { /** * Toggles children of a node. */ -ImageFileChangeTree.prototype.toggle_ = function(d) { +FileTreeBase.prototype.toggle_ = function(d) { if (d.children) { d._children = d.children; d.children = null; @@ -1237,6 +1228,101 @@ ImageFileChangeTree.prototype.toggle_ = function(d) { }; +//////////////////////////////////////////////////////////////////////////////// + +function ImageFileChangeTree(image, changes) { + FileTreeBase.apply(this); + + /** + * The parent image. + */ + this.image_ = image; + + /** + * The changes being drawn. + */ + this.changes_ = changes; +} + +$.extend(ImageFileChangeTree.prototype, FileTreeBase.prototype); + +/** + * Builds all the file nodes from the changes. + */ +ImageFileChangeTree.prototype.buildAllNodes = function() { + for (var i = 0; i < this.changes_.length; ++i) { + var filepath = this.changes_[i].file; + var node = this.buildNodes_(filepath); + node.kind = this.changes_[i].kind; + } +}; + + +/** + * Determines the icon for a node. + */ +ImageFileChangeTree.prototype.determineIcon = function(d) { + var icon = { + 'added': 'plus-square', + 'removed': 'minus-square', + 'changed': 'pencil-square' + }; + + return ''; +}; + + +/** + * Returns the max height of the tree. + */ +ImageFileChangeTree.prototype.getNodesHeight = function() { + return this.changes_.length; +}; + + +//////////////////////////////////////////////////////////////////////////////// + +function FileTree(files) { + FileTreeBase.apply(this); + + /** + * The set of file paths found in the tree. + */ + this.files_ = files; +} + +$.extend(FileTree.prototype, FileTreeBase.prototype); + + +/** + * Builds all the file nodes from the paths. + */ +FileTree.prototype.buildAllNodes = function() { + for (var i = 0; i < this.files_.length; ++i) { + var filepath = this.files_[i]; + if (filepath[filepath.length - 1] == '/') { continue; } + var node = this.buildNodes_(filepath); + node.kind = 'file'; + } +}; + + +/** + * Determines the icon for a node. + */ +FileTree.prototype.determineIcon = function(d) { + return ''; +}; + + +/** + * Returns the max height of the tree. + */ +FileTree.prototype.getNodesHeight = function() { + return this.files_.length; +}; + + //////////////////////////////////////////////////////////////////////////////// /** @@ -1297,12 +1383,12 @@ RepositoryUsageChart.prototype.drawInternal_ = function() { .text(this.count_ + ' / ' + this.total_); var path = this.g_.datum(data).selectAll("path") - .data(pie) - .enter().append("path") - .attr("fill", function(d, i) { return color(i); }) - .attr("class", function(d, i) { return 'arc-' + i; }) - .attr("d", arc) - .each(function(d) { this._current = d; }); // store the initial angles + .data(pie) + .enter().append("path") + .attr("fill", function(d, i) { return color(i); }) + .attr("class", function(d, i) { return 'arc-' + i; }) + .attr("d", arc) + .each(function(d) { this._current = d; }); // store the initial angles this.path_ = path; this.text_ = text; @@ -1314,7 +1400,7 @@ RepositoryUsageChart.prototype.drawInternal_ = function() { // Update the text. this.text_.text(this.count_ + ' / ' + this.total_); } - + this.drawn_ = true; }; @@ -1414,7 +1500,7 @@ LogUsageChart.prototype.buildData_ = function(logs) { dataMap[key] = found; dataArray.push(found); } - + found.values.push({ 'x': entry.adjusted, 'y': entry.count @@ -1454,7 +1540,7 @@ LogUsageChart.prototype.buildData_ = function(logs) { return a['x'].getDate() - b['x'].getDate(); }); } - + return this.data_ = dataArray; }; @@ -1538,7 +1624,7 @@ LogUsageChart.prototype.handleStateChange_ = function(e) { allowed[this.data_[i].kind] = true; } } - + $(this).trigger({ 'type': 'filteringChanged', 'allowed': allowed @@ -1596,8 +1682,8 @@ LogUsageChart.prototype.draw = function(container, logData, startDate, endDate) d3.select('#bar-chart svg') .datum(data) .transition() - .duration(500) - .call(chart); + .duration(500) + .call(chart); nv.utils.windowResize(chart.update); diff --git a/static/lib/jszip.min.js b/static/lib/jszip.min.js new file mode 100755 index 000000000..383897b11 --- /dev/null +++ b/static/lib/jszip.min.js @@ -0,0 +1,14 @@ +/*! + +JSZip - A Javascript class for generating and reading zip files + + +(c) 2009-2012 Stuart Knightley +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. + +JSZip uses the library zlib.js released under the following license : +zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License +*/ +!function(a){"object"==typeof exports?module.exports=a():"function"==typeof define&&define.amd?define(a):"undefined"!=typeof window?window.JSZip=a():"undefined"!=typeof global?global.JSZip=a():"undefined"!=typeof self&&(self.JSZip=a())}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g>2,g=(3&b)<<4|c>>4,h=(15&c)<<2|e>>6,i=63&e,isNaN(c)?h=i=64:isNaN(e)&&(i=64),j=j+d.charAt(f)+d.charAt(g)+d.charAt(h)+d.charAt(i);return j},c.decode=function(a){var b,c,e,f,g,h,i,j="",k=0;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");k>4,c=(15&g)<<4|h>>2,e=(3&h)<<6|i,j+=String.fromCharCode(b),64!=h&&(j+=String.fromCharCode(c)),64!=i&&(j+=String.fromCharCode(e));return j}},{}],2:[function(a,b){"use strict";function c(){this.compressedSize=0,this.uncompressedSize=0,this.crc32=0,this.compressionMethod=null,this.compressedContent=null}c.prototype={getContent:function(){return null},getCompressedContent:function(){return null}},b.exports=c},{}],3:[function(a,b,c){"use strict";c.STORE={magic:"\x00\x00",compress:function(a){return a},uncompress:function(a){return a},compressInputType:null,uncompressInputType:null},c.DEFLATE=a("./flate")},{"./flate":6}],4:[function(a,b){"use strict";function c(){this.data=null,this.length=0,this.index=0}var d=a("./utils");c.prototype={checkOffset:function(a){this.checkIndex(this.index+a)},checkIndex:function(a){if(this.lengtha)throw new Error("End of data reached (data length = "+this.length+", asked index = "+a+"). Corrupted zip ?")},setIndex:function(a){this.checkIndex(a),this.index=a},skip:function(a){this.setIndex(this.index+a)},byteAt:function(){},readInt:function(a){var b,c=0;for(this.checkOffset(a),b=this.index+a-1;b>=this.index;b--)c=(c<<8)+this.byteAt(b);return this.index+=a,c},readString:function(a){return d.transformTo("string",this.readData(a))},readData:function(){},lastIndexOfSignature:function(){},readDate:function(){var a=this.readInt(4);return new Date((a>>25&127)+1980,(a>>21&15)-1,a>>16&31,a>>11&31,a>>5&63,(31&a)<<1)}},b.exports=c},{"./utils":14}],5:[function(a,b,c){"use strict";c.base64=!1,c.binary=!1,c.dir=!1,c.date=null,c.compression=null},{}],6:[function(a,b,c){"use strict";var d="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,e=a("zlibjs/bin/rawdeflate.min").Zlib,f=a("zlibjs/bin/rawinflate.min").Zlib;c.uncompressInputType=d?"uint8array":"array",c.compressInputType=d?"uint8array":"array",c.magic="\b\x00",c.compress=function(a){var b=new e.RawDeflate(a);return b.compress()},c.uncompress=function(a){var b=new f.RawInflate(a);return b.decompress()}},{"zlibjs/bin/rawdeflate.min":19,"zlibjs/bin/rawinflate.min":20}],7:[function(a,b){"use strict";function c(a,b){this.files={},this.root="",a&&this.load(a,b),this.clone=function(){var a=new c;for(var b in this)"function"!=typeof this[b]&&(a[b]=this[b]);return a}}c.prototype=a("./object"),c.prototype.load=a("./load"),c.support=a("./support"),c.defaults=a("./defaults"),c.utils=a("./utils"),c.base64=a("./base64"),c.compressions=a("./compressions"),b.exports=c},{"./base64":1,"./compressions":3,"./defaults":5,"./load":8,"./object":9,"./support":12,"./utils":14}],8:[function(a,b){"use strict";var c=a("./base64"),d=a("./zipEntries");b.exports=function(a,b){var e,f,g,h;for(b=b||{},b.base64&&(a=c.decode(a)),f=new d(a,b),e=f.files,g=0;gc;c++)d+=String.fromCharCode(255&a),a>>>=8;return d},r=function(){var a,b,c={};for(a=0;a0?a.substring(0,b):""},v=function(a){return"/"!=a.slice(-1)&&(a+="/"),this.files[a]||t.call(this,a,null,{dir:!0}),this.files[a]},w=function(a,b){var c,d=new k;return a._data instanceof k?(d.uncompressedSize=a._data.uncompressedSize,d.crc32=a._data.crc32,0===d.uncompressedSize||a.options.dir?(b=j.STORE,d.compressedContent="",d.crc32=0):a._data.compressionMethod===b.magic?d.compressedContent=a._data.getCompressedContent():(c=a._data.getContent(),d.compressedContent=b.compress(f.transformTo(b.compressInputType,c)))):(c=n(a),(!c||0===c.length||a.options.dir)&&(b=j.STORE,c=""),d.uncompressedSize=c.length,d.crc32=this.crc32(c),d.compressedContent=b.compress(f.transformTo(b.compressInputType,c))),d.compressedSize=d.compressedContent.length,d.compressionMethod=b.magic,d},x=function(a,b,c,d){var e,f,h=(c.compressedContent,this.utf8encode(b.name)),i=h!==b.name,j=b.options,k="",l="";e=j.date.getHours(),e<<=6,e|=j.date.getMinutes(),e<<=5,e|=j.date.getSeconds()/2,f=j.date.getFullYear()-1980,f<<=4,f|=j.date.getMonth()+1,f<<=5,f|=j.date.getDate(),i&&(l=q(1,1)+q(this.crc32(h),4)+h,k+="up"+q(l.length,2)+l);var m="";m+="\n\x00",m+=i?"\x00\b":"\x00\x00",m+=c.compressionMethod,m+=q(e,2),m+=q(f,2),m+=q(c.crc32,4),m+=q(c.compressedSize,4),m+=q(c.uncompressedSize,4),m+=q(h.length,2),m+=q(k.length,2);var n=g.LOCAL_FILE_HEADER+m+h+k,o=g.CENTRAL_FILE_HEADER+"\x00"+m+"\x00\x00\x00\x00\x00\x00"+(b.options.dir===!0?"\x00\x00\x00":"\x00\x00\x00\x00")+q(d,4)+h+k;return{fileRecord:n,dirRecord:o,compressedObject:c}},y=function(){this.data=[]};y.prototype={append:function(a){a=f.transformTo("string",a),this.data.push(a)},finalize:function(){return this.data.join("")}};var z=function(a){this.data=new Uint8Array(a),this.index=0};z.prototype={append:function(a){0!==a.length&&(a=f.transformTo("uint8array",a),this.data.set(a,this.index),this.index+=a.length)},finalize:function(){return this.data}};var A={load:function(){throw new Error("Load method is not defined. Is the file jszip-load.js included ?")},filter:function(a){var b,c,d,e,f=[];for(b in this.files)this.files.hasOwnProperty(b)&&(d=this.files[b],e=new p(d.name,d._data,r(d.options)),c=b.slice(this.root.length,b.length),b.slice(0,this.root.length)===this.root&&a(c,e)&&f.push(e));return f},file:function(a,b,c){if(1===arguments.length){if(f.isRegExp(a)){var d=a;return this.filter(function(a,b){return!b.options.dir&&d.test(a)})}return this.filter(function(b,c){return!c.options.dir&&b===a})[0]||null}return a=this.root+a,t.call(this,a,b,c),this},folder:function(a){if(!a)return this;if(f.isRegExp(a))return this.filter(function(b,c){return c.options.dir&&a.test(b)});var b=this.root+a,c=v.call(this,b),d=this.clone();return d.root=c.name,d},remove:function(a){a=this.root+a;var b=this.files[a];if(b||("/"!=a.slice(-1)&&(a+="/"),b=this.files[a]),b)if(b.options.dir)for(var c=this.filter(function(b,c){return c.name.slice(0,a.length)===a}),d=0;di;i++)h=c?a[i]:a.charCodeAt(i),g=255&(b^h),e=d[g],b=b>>>8^e;return-1^b},utf8encode:function(a){if(c){var b=c.encode(a);return f.transformTo("string",b)}if(e.nodebuffer)return f.transformTo("string",l(a,"utf-8"));for(var d=[],g=0,h=0;hi?d[g++]=String.fromCharCode(i):i>127&&2048>i?(d[g++]=String.fromCharCode(i>>6|192),d[g++]=String.fromCharCode(63&i|128)):(d[g++]=String.fromCharCode(i>>12|224),d[g++]=String.fromCharCode(i>>6&63|128),d[g++]=String.fromCharCode(63&i|128))}return d.join("")},utf8decode:function(a){var b=[],c=0,g=f.getTypeOf(a),h="string"!==g,i=0,j=0,k=0,l=0;if(d)return d.decode(f.transformTo("uint8array",a));if(e.nodebuffer)return f.transformTo("nodebuffer",a).toString("utf-8");for(;ij?(b[c++]=String.fromCharCode(j),i++):j>191&&224>j?(k=h?a[i+1]:a.charCodeAt(i+1),b[c++]=String.fromCharCode((31&j)<<6|63&k),i+=2):(k=h?a[i+1]:a.charCodeAt(i+1),l=h?a[i+2]:a.charCodeAt(i+2),b[c++]=String.fromCharCode((15&j)<<12|(63&k)<<6|63&l),i+=3);return b.join("")}};b.exports=A},{"./base64":1,"./compressedObject":2,"./compressions":3,"./defaults":5,"./nodeBuffer":17,"./signature":10,"./support":12,"./utils":14}],10:[function(a,b,c){"use strict";c.LOCAL_FILE_HEADER="PK",c.CENTRAL_FILE_HEADER="PK",c.CENTRAL_DIRECTORY_END="PK",c.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",c.ZIP64_CENTRAL_DIRECTORY_END="PK",c.DATA_DESCRIPTOR="PK\b"},{}],11:[function(a,b){"use strict";function c(a,b){this.data=a,b||(this.data=e.string2binary(this.data)),this.length=this.data.length,this.index=0}var d=a("./dataReader"),e=a("./utils");c.prototype=new d,c.prototype.byteAt=function(a){return this.data.charCodeAt(a)},c.prototype.lastIndexOfSignature=function(a){return this.data.lastIndexOf(a)},c.prototype.readData=function(a){this.checkOffset(a);var b=this.data.slice(this.index,this.index+a);return this.index+=a,b},b.exports=c},{"./dataReader":4,"./utils":14}],12:[function(a,b,c){var d=a("__browserify_process");if(c.base64=!0,c.array=!0,c.string=!0,c.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,c.nodebuffer=!d.browser,c.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)c.blob=!1;else{var e=new ArrayBuffer(0);try{c.blob=0===new Blob([e],{type:"application/zip"}).size}catch(f){try{var g=window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder,h=new g;h.append(e),c.blob=0===h.getBlob("application/zip").size}catch(f){c.blob=!1}}}},{__browserify_process:18}],13:[function(a,b){"use strict";function c(a){a&&(this.data=a,this.length=this.data.length,this.index=0)}var d=a("./dataReader");c.prototype=new d,c.prototype.byteAt=function(a){return this.data[a]},c.prototype.lastIndexOfSignature=function(a){for(var b=a.charCodeAt(0),c=a.charCodeAt(1),d=a.charCodeAt(2),e=a.charCodeAt(3),f=this.length-4;f>=0;--f)if(this.data[f]===b&&this.data[f+1]===c&&this.data[f+2]===d&&this.data[f+3]===e)return f;return-1},c.prototype.readData=function(a){this.checkOffset(a);var b=this.data.subarray(this.index,this.index+a);return this.index+=a,b},b.exports=c},{"./dataReader":4}],14:[function(a,b,c){"use strict";function d(a){return a}function e(a,b){for(var c=0;cg&&b>1;)try{d.push("array"===f||"nodebuffer"===f?String.fromCharCode.apply(null,a.slice(g,Math.min(g+b,e))):String.fromCharCode.apply(null,a.subarray(g,Math.min(g+b,e)))),g+=b}catch(i){b=Math.floor(b/2)}return d.join("")}function g(a,b){for(var c=0;cb?"0":"")+b.toString(16).toUpperCase();return d},c.findCompression=function(a){for(var b in i)if(i.hasOwnProperty(b)&&i[b].magic===a)return i[b];return null},c.isRegExp=function(a){return"[object RegExp]"===Object.prototype.toString.call(a)}},{"./compressions":3,"./nodeBuffer":17,"./support":12}],15:[function(a,b){"use strict";function c(a,b){this.files=[],this.loadOptions=b,a&&this.load(a)}var d=a("./stringReader"),e=a("./nodeBufferReader"),f=a("./uint8ArrayReader"),g=a("./utils"),h=a("./signature"),i=a("./zipEntry"),j=a("./support");c.prototype={checkSignature:function(a){var b=this.reader.readString(4);if(b!==a)throw new Error("Corrupted zip or bug : unexpected signature ("+g.pretty(b)+", expected "+g.pretty(a)+")")},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2),this.zipComment=this.reader.readString(this.zipCommentLength)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.versionMadeBy=this.reader.readString(2),this.versionNeeded=this.reader.readInt(2),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var a,b,c,d=this.zip64EndOfCentralSize-44,e=0;d>e;)a=this.reader.readInt(2),b=this.reader.readInt(4),c=this.reader.readString(b),this.zip64ExtensibleData[a]={id:a,length:b,value:c}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),this.disksCount>1)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var a,b;for(a=0;a0)){var d=c.shift();d()}},!0),function(a){c.push(a),window.postMessage("process-tick","*")}}return function(a){setTimeout(a,0)}}(),c.title="browser",c.browser=!0,c.env={},c.argv=[],c.binding=function(){throw new Error("process.binding is not supported")},c.cwd=function(){return"/"},c.chdir=function(){throw new Error("process.chdir is not supported")}},{}],19:[function(){/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */ +(function(){"use strict";function a(a,b){var c=a.split("."),d=n;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||b===l?d=d[e]?d[e]:d[e]={}:d[e]=b}function b(a,b){if(this.index="number"==typeof b?b:0,this.d=0,this.buffer=a instanceof(o?Uint8Array:Array)?a:new(o?Uint8Array:Array)(32768),2*this.buffer.length<=this.index)throw Error("invalid index");this.buffer.length<=this.index&&c(this)}function c(a){var b,c=a.buffer,d=c.length,e=new(o?Uint8Array:Array)(d<<1);if(o)e.set(c);else for(b=0;d>b;++b)e[b]=c[b];return a.buffer=e}function d(a){this.buffer=new(o?Uint16Array:Array)(2*a),this.length=0}function e(a,b){this.e=w,this.f=0,this.input=o&&a instanceof Array?new Uint8Array(a):a,this.c=0,b&&(b.lazy&&(this.f=b.lazy),"number"==typeof b.compressionType&&(this.e=b.compressionType),b.outputBuffer&&(this.b=o&&b.outputBuffer instanceof Array?new Uint8Array(b.outputBuffer):b.outputBuffer),"number"==typeof b.outputIndex&&(this.c=b.outputIndex)),this.b||(this.b=new(o?Uint8Array:Array)(32768))}function f(a,b){this.length=a,this.g=b}function g(a,b){function c(a,b){var c,d=a.g,e=[],f=0;c=z[a.length],e[f++]=65535&c,e[f++]=c>>16&255,e[f++]=c>>24;var g;switch(m){case 1===d:g=[0,d-1,0];break;case 2===d:g=[1,d-2,0];break;case 3===d:g=[2,d-3,0];break;case 4===d:g=[3,d-4,0];break;case 6>=d:g=[4,d-5,1];break;case 8>=d:g=[5,d-7,1];break;case 12>=d:g=[6,d-9,2];break;case 16>=d:g=[7,d-13,2];break;case 24>=d:g=[8,d-17,3];break;case 32>=d:g=[9,d-25,3];break;case 48>=d:g=[10,d-33,4];break;case 64>=d:g=[11,d-49,4];break;case 96>=d:g=[12,d-65,5];break;case 128>=d:g=[13,d-97,5];break;case 192>=d:g=[14,d-129,6];break;case 256>=d:g=[15,d-193,6];break;case 384>=d:g=[16,d-257,7];break;case 512>=d:g=[17,d-385,7];break;case 768>=d:g=[18,d-513,8];break;case 1024>=d:g=[19,d-769,8];break;case 1536>=d:g=[20,d-1025,9];break;case 2048>=d:g=[21,d-1537,9];break;case 3072>=d:g=[22,d-2049,10];break;case 4096>=d:g=[23,d-3073,10];break;case 6144>=d:g=[24,d-4097,11];break;case 8192>=d:g=[25,d-6145,11];break;case 12288>=d:g=[26,d-8193,12];break;case 16384>=d:g=[27,d-12289,12];break;case 24576>=d:g=[28,d-16385,13];break;case 32768>=d:g=[29,d-24577,13];break;default:throw"invalid distance"}c=g,e[f++]=c[0],e[f++]=c[1],e[f++]=c[2];var h,i;for(h=0,i=e.length;i>h;++h)r[s++]=e[h];u[e[0]]++,v[e[3]]++,t=a.length+b-1,n=null}var d,e,f,g,i,j,k,n,p,q={},r=o?new Uint16Array(2*b.length):[],s=0,t=0,u=new(o?Uint32Array:Array)(286),v=new(o?Uint32Array:Array)(30),w=a.f;if(!o){for(f=0;285>=f;)u[f++]=0;for(f=0;29>=f;)v[f++]=0}for(u[256]=1,d=0,e=b.length;e>d;++d){for(f=i=0,g=3;g>f&&d+f!==e;++f)i=i<<8|b[d+f];if(q[i]===l&&(q[i]=[]),j=q[i],!(0=e){for(n&&c(n,-1),f=0,g=e-d;g>f;++f)p=b[d+f],r[s++]=p,++u[p];break}0h;h++){if(d=c[j-h-1],g=3,k>3){for(i=k;i>3;i--)if(a[d+i-1]!==a[b+i-1])continue a;g=k}for(;258>g&&l>b+g&&a[d+g]===a[b+g];)++g;if(g>k&&(e=d,k=g),258===g)break}return new f(k,b-e)}function i(a,b){var c,e,f,g,h,i=a.length,k=new d(572),l=new(o?Uint8Array:Array)(i);if(!o)for(g=0;i>g;g++)l[g]=0;for(g=0;i>g;++g)0g;++g)c[g]=k.pop(),e[g]=c[g].value;for(f=j(e,e.length,b),g=0,h=c.length;h>g;++g)l[c[g].index]=f[g];return l}function j(a,b,c){function d(a){var c=n[a][p[a]];c===b?(d(a+1),d(a+1)):--l[c],++p[a]}var e,f,g,h,i,j=new(o?Uint16Array:Array)(c),k=new(o?Uint8Array:Array)(c),l=new(o?Uint8Array:Array)(b),m=Array(c),n=Array(c),p=Array(c),q=(1<f;++f)r>q?k[f]=0:(k[f]=1,q-=r),q<<=1,j[c-2-f]=(j[c-1-f]/2|0)+b;for(j[0]=k[0],m[0]=Array(j[0]),n[0]=Array(j[0]),f=1;c>f;++f)j[f]>2*j[f-1]+k[f]&&(j[f]=2*j[f-1]+k[f]),m[f]=Array(j[f]),n[f]=Array(j[f]);for(e=0;b>e;++e)l[e]=c;for(g=0;ge;++e)p[e]=0;for(1===k[c-1]&&(--l[0],++p[c-1]),f=c-2;f>=0;--f){for(h=e=0,i=p[f+1],g=0;ga[e]?(m[f][g]=h,n[f][g]=b,i+=2):(m[f][g]=a[e],n[f][g]=e,++e);p[f]=0,1===k[f]&&d(f)}return l}function k(a){var b,c,d,e,f=new(o?Uint16Array:Array)(a.length),g=[],h=[],i=0;for(b=0,c=a.length;c>b;b++)g[a[b]]=(0|g[a[b]])+1;for(b=1,c=16;c>=b;b++)h[b]=i,i+=0|g[b],i<<=1;for(b=0,c=a.length;c>b;b++)for(i=h[a[b]],h[a[b]]+=1,d=f[b]=0,e=a[b];e>d;d++)f[b]=f[b]<<1|1&i,i>>>=1;return f}var l=void 0,m=!0,n=this,o="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array&&"undefined"!=typeof DataView;b.prototype.a=function(a,b,d){var e,f=this.buffer,g=this.index,h=this.d,i=f[g];if(d&&b>1&&(a=b>8?(u[255&a]<<24|u[a>>>8&255]<<16|u[a>>>16&255]<<8|u[a>>>24&255])>>32-b:u[a]>>8-b),8>b+h)i=i<e;++e)i=i<<1|a>>b-e-1&1,8===++h&&(h=0,f[g++]=u[i],i=0,g===f.length&&(f=c(this)));f[g]=i,this.buffer=f,this.d=h,this.index=g},b.prototype.finish=function(){var a,b=this.buffer,c=this.index;return 0p;++p){for(var r=p,s=r,t=7,r=r>>>1;r;r>>>=1)s<<=1,s|=1&r,--t;q[p]=(s<>>0}var u=q;d.prototype.getParent=function(a){return 2*((a-2)/4|0)},d.prototype.push=function(a,b){var c,d,e,f=this.buffer;for(c=this.length,f[this.length++]=b,f[this.length++]=a;c>0&&(d=this.getParent(c),f[c]>f[d]);)e=f[c],f[c]=f[d],f[d]=e,e=f[c+1],f[c+1]=f[d+1],f[d+1]=e,c=d;return this.length},d.prototype.pop=function(){var a,b,c,d,e,f=this.buffer;for(b=f[0],a=f[1],this.length-=2,f[0]=f[this.length],f[1]=f[this.length+1],e=0;(d=2*e+2,!(d>=this.length))&&(d+2f[d]&&(d+=2),f[d]>f[e]);)c=f[e],f[e]=f[d],f[d]=c,c=f[e+1],f[e+1]=f[d+1],f[d+1]=c,e=d;return{index:a,value:b,length:this.length}};var v,w=2,x=[];for(v=0;288>v;v++)switch(m){case 143>=v:x.push([v+48,8]);break;case 255>=v:x.push([v-144+400,9]);break;case 279>=v:x.push([v-256+0,7]);break;case 287>=v:x.push([v-280+192,8]);break;default:throw"invalid literal: "+v}e.prototype.h=function(){var a,c,d,e,f=this.input;switch(this.e){case 0:for(d=0,e=f.length;e>d;){c=o?f.subarray(d,d+65535):f.slice(d,d+65535),d+=c.length;var h=c,j=d===e,n=l,p=l,q=l,r=l,s=l,t=this.b,u=this.c;if(o){for(t=new Uint8Array(this.b.buffer);t.length<=u+h.length+5;)t=new Uint8Array(t.length<<1);t.set(this.b)}if(n=j?1:0,t[u++]=0|n,p=h.length,q=~p+65536&65535,t[u++]=255&p,t[u++]=p>>>8&255,t[u++]=255&q,t[u++]=q>>>8&255,o)t.set(h,u),u+=h.length,t=t.subarray(0,u);else{for(r=0,s=h.length;s>r;++r)t[u++]=h[r];t.length=u}this.c=u,this.b=t}break;case 1:var v=new b(o?new Uint8Array(this.b.buffer):this.b,this.c);v.a(1,1,m),v.a(1,2,m);var y,z,A,B=g(this,f);for(y=0,z=B.length;z>y;y++)if(A=B[y],b.prototype.a.apply(v,x[A]),A>256)v.a(B[++y],B[++y],m),v.a(B[++y],5),v.a(B[++y],B[++y],m);else if(256===A)break;this.b=v.finish(),this.c=this.b.length;break;case w:var C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R=new b(o?new Uint8Array(this.b.buffer):this.b,this.c),S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],T=Array(19);for(C=w,R.a(1,1,m),R.a(C,2,m),D=g(this,f),H=i(this.j,15),I=k(H),J=i(this.i,7),K=k(J),E=286;E>257&&0===H[E-1];E--);for(F=30;F>1&&0===J[F-1];F--);var U,V,W,X,Y,Z,$=E,_=F,ab=new(o?Uint32Array:Array)($+_),bb=new(o?Uint32Array:Array)(316),cb=new(o?Uint8Array:Array)(19);for(U=V=0;$>U;U++)ab[V++]=H[U];for(U=0;_>U;U++)ab[V++]=J[U];if(!o)for(U=0,X=cb.length;X>U;++U)cb[U]=0;for(U=Y=0,X=ab.length;X>U;U+=V){for(V=1;X>U+V&&ab[U+V]===ab[U];++V);if(W=V,0===ab[U])if(3>W)for(;00;)Z=138>W?W:138,Z>W-3&&W>Z&&(Z=W-3),10>=Z?(bb[Y++]=17,bb[Y++]=Z-3,cb[17]++):(bb[Y++]=18,bb[Y++]=Z-11,cb[18]++),W-=Z;else if(bb[Y++]=ab[U],cb[ab[U]]++,W--,3>W)for(;00;)Z=6>W?W:6,Z>W-3&&W>Z&&(Z=W-3),bb[Y++]=16,bb[Y++]=Z-3,cb[16]++,W-=Z}for(a=o?bb.subarray(0,Y):bb.slice(0,Y),L=i(cb,7),P=0;19>P;P++)T[P]=L[S[P]];for(G=19;G>4&&0===T[G-1];G--);for(M=k(L),R.a(E-257,5,m),R.a(F-1,5,m),R.a(G-4,4,m),P=0;G>P;P++)R.a(T[P],3,m);for(P=0,Q=a.length;Q>P;P++)if(N=a[P],R.a(M[N],L[N],m),N>=16){switch(P++,N){case 16:O=2;break;case 17:O=3;break;case 18:O=7;break;default:throw"invalid code: "+N}R.a(a[P],O,m)}var db,eb,fb,gb,hb,ib,jb,kb,lb=[I,H],mb=[K,J];for(hb=lb[0],ib=lb[1],jb=mb[0],kb=mb[1],db=0,eb=D.length;eb>db;++db)if(fb=D[db],R.a(hb[fb],ib[fb],m),fb>256)R.a(D[++db],D[++db],m),gb=D[++db],R.a(jb[gb],kb[gb],m),R.a(D[++db],D[++db],m);else if(256===fb)break;this.b=R.finish(),this.c=this.b.length;break;default:throw"invalid compression type"}return this.b};var y=function(){function a(a){switch(m){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272,a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:throw"invalid length: "+a}}var b,c,d=[];for(b=3;258>=b;b++)c=a(b),d[b]=c[2]<<24|c[1]<<16|c[0];return d}(),z=o?new Uint32Array(y):y;a("Zlib.RawDeflate",e),a("Zlib.RawDeflate.prototype.compress",e.prototype.h);var A,B,C,D,E={NONE:0,FIXED:1,DYNAMIC:w};if(Object.keys)A=Object.keys(E);else for(B in A=[],C=0,E)A[C++]=B;for(C=0,D=A.length;D>C;++C)B=A[C],a("Zlib.RawDeflate.CompressionType."+B,E[B])}).call(this)},{}],20:[function(){/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */ +(function(){"use strict";function a(a,b){var c=a.split("."),d=g;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b}function b(a){var b,c,d,e,f,g,i,j,k,l=a.length,m=0,n=Number.POSITIVE_INFINITY;for(j=0;l>j;++j)a[j]>m&&(m=a[j]),a[j]=d;){for(j=0;l>j;++j)if(a[j]===d){for(g=0,i=e,k=0;d>k;++k)g=g<<1|1&i,i>>=1;for(k=g;b>k;k+=f)c[k]=d<<16|j;++e}++d,e<<=1,f<<=1}return[c,m,n]}function c(a,b){switch(this.g=[],this.h=32768,this.c=this.f=this.d=this.k=0,this.input=h?new Uint8Array(a):a,this.l=!1,this.i=j,this.p=!1,(b||!(b={}))&&(b.index&&(this.d=b.index),b.bufferSize&&(this.h=b.bufferSize),b.bufferType&&(this.i=b.bufferType),b.resize&&(this.p=b.resize)),this.i){case i:this.a=32768,this.b=new(h?Uint8Array:Array)(32768+this.h+258);break;case j:this.a=0,this.b=new(h?Uint8Array:Array)(this.h),this.e=this.u,this.m=this.r,this.j=this.s;break;default:throw Error("invalid inflate mode")}}function d(a,b){for(var c,d=a.f,e=a.c,f=a.input,g=a.d,h=f.length;b>e;){if(g>=h)throw Error("input buffer is broken");d|=f[g++]<>>b,a.c=e-b,a.d=g,c}function e(a,b){for(var c,d,e=a.f,f=a.c,g=a.input,h=a.d,i=g.length,j=b[0],k=b[1];k>f&&!(h>=i);)e|=g[h++]<>>16,a.f=e>>d,a.c=f-d,a.d=h,65535&c}function f(a){function c(a,b,c){var f,g,h,i;for(i=0;a>i;)switch(f=e(this,b)){case 16:for(h=3+d(this,2);h--;)c[i++]=g;break;case 17:for(h=3+d(this,3);h--;)c[i++]=0;g=0;break;case 18:for(h=11+d(this,7);h--;)c[i++]=0;g=0;break;default:g=c[i++]=f}return c}var f,g,i,j,k=d(a,5)+257,l=d(a,5)+1,m=d(a,4)+4,o=new(h?Uint8Array:Array)(n.length);for(j=0;m>j;++j)o[n[j]]=d(a,3);if(!h)for(j=m,m=o.length;m>j;++j)o[n[j]]=0;f=b(o),g=new(h?Uint8Array:Array)(k),i=new(h?Uint8Array:Array)(l),a.j(b(c.call(a,k,f,g)),b(c.call(a,l,f,i)))}var g=this,h="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array&&"undefined"!=typeof DataView,i=0,j=1;c.prototype.t=function(){for(;!this.l;){var a=d(this,3);switch(1&a&&(this.l=!0),a>>>=1){case 0:var b=this.input,c=this.d,e=this.b,g=this.a,k=b.length,l=void 0,m=void 0,n=e.length,o=void 0;if(this.c=this.f=0,c+1>=k)throw Error("invalid uncompressed block header: LEN");if(l=b[c++]|b[c++]<<8,c+1>=k)throw Error("invalid uncompressed block header: NLEN");if(m=b[c++]|b[c++]<<8,l===~m)throw Error("invalid uncompressed block header: length verify");if(c+l>b.length)throw Error("input buffer is broken");switch(this.i){case i:for(;g+l>e.length;){if(o=n-g,l-=o,h)e.set(b.subarray(c,c+o),g),g+=o,c+=o;else for(;o--;)e[g++]=b[c++];this.a=g,e=this.e(),g=this.a}break;case j:for(;g+l>e.length;)e=this.e({o:2});break;default:throw Error("invalid inflate mode")}if(h)e.set(b.subarray(c,c+l),g),g+=l,c+=l;else for(;l--;)e[g++]=b[c++];this.d=c,this.a=g,this.b=e;break;case 1:this.j(z,B);break;case 2:f(this);break;default:throw Error("unknown BTYPE: "+a)}}return this.m()};var k,l,m=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],n=h?new Uint16Array(m):m,o=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],p=h?new Uint16Array(o):o,q=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],r=h?new Uint8Array(q):q,s=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],t=h?new Uint16Array(s):s,u=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],v=h?new Uint8Array(u):u,w=new(h?Uint8Array:Array)(288);for(k=0,l=w.length;l>k;++k)w[k]=143>=k?8:255>=k?9:279>=k?7:8;var x,y,z=b(w),A=new(h?Uint8Array:Array)(30);for(x=0,y=A.length;y>x;++x)A[x]=5;var B=b(A);c.prototype.j=function(a,b){var c=this.b,f=this.a;this.n=a;for(var g,h,i,j,k=c.length-258;256!==(g=e(this,a));)if(256>g)f>=k&&(this.a=f,c=this.e(),f=this.a),c[f++]=g;else for(h=g-257,j=p[h],0=k&&(this.a=f,c=this.e(),f=this.a);j--;)c[f]=c[f++-i];for(;8<=this.c;)this.c-=8,this.d--;this.a=f},c.prototype.s=function(a,b){var c=this.b,f=this.a;this.n=a;for(var g,h,i,j,k=c.length;256!==(g=e(this,a));)if(256>g)f>=k&&(c=this.e(),k=c.length),c[f++]=g;else for(h=g-257,j=p[h],0k&&(c=this.e(),k=c.length);j--;)c[f]=c[f++-i];for(;8<=this.c;)this.c-=8,this.d--;this.a=f},c.prototype.e=function(){var a,b,c=new(h?Uint8Array:Array)(this.a-32768),d=this.a-32768,e=this.b;if(h)c.set(e.subarray(32768,c.length));else for(a=0,b=c.length;b>a;++a)c[a]=e[a+32768];if(this.g.push(c),this.k+=c.length,h)e.set(e.subarray(d,d+32768));else for(a=0;32768>a;++a)e[a]=e[d+a];return this.a=32768,e},c.prototype.u=function(a){var b,c,d,e,f=this.input.length/this.d+1|0,g=this.input,i=this.b;return a&&("number"==typeof a.o&&(f=a.o),"number"==typeof a.q&&(f+=a.q)),2>f?(c=(g.length-this.d)/this.n[2],e=258*(c/2)|0,d=eb;++b)for(a=i[b],d=0,e=a.length;e>d;++d)j[f++]=a[d];for(b=32768,c=this.a;c>b;++b)j[f++]=g[b];return this.g=[],this.buffer=j},c.prototype.r=function(){var a,b=this.a;return h?this.p?(a=new Uint8Array(b),a.set(this.b.subarray(0,b))):a=this.b.subarray(0,b):(this.b.length>b&&(this.b.length=b),a=this.b),this.buffer=a},a("Zlib.RawInflate",c),a("Zlib.RawInflate.prototype.decompress",c.prototype.t);var C,D,E,F,G={ADAPTIVE:j,BLOCK:i};if(Object.keys)C=Object.keys(G);else for(D in C=[],E=0,G)C[E++]=D;for(E=0,F=C.length;F>E;++E)D=C[E],a("Zlib.RawInflate.BufferType."+D,G[D])}).call(this)},{}]},{},[7])(7)}); \ No newline at end of file diff --git a/static/partials/build-package.html b/static/partials/build-package.html new file mode 100644 index 000000000..69091167d --- /dev/null +++ b/static/partials/build-package.html @@ -0,0 +1,45 @@ +
+
+ You do not have permission to view this page +
+
+
+ +

+ + +

+
+ +
+ Downloading build pack: +
+
+
+
+
+ +
+ Error: Could not download the build pack +
+ +
+ + +
+ +
+
{{ dockerFileContents }}
+ No Dockerfile found in the build pack +
+ + +
+
+
+
+
+
diff --git a/static/partials/repo-build.html b/static/partials/repo-build.html index fb3db86d7..dbd04342d 100644 --- a/static/partials/repo-build.html +++ b/static/partials/repo-build.html @@ -43,6 +43,12 @@
Started: + + + Build Package +
diff --git a/templates/index.html b/templates/index.html index d3e3de75c..11db05e41 100644 --- a/templates/index.html +++ b/templates/index.html @@ -23,8 +23,8 @@ - + @@ -32,6 +32,7 @@ + diff --git a/test/data/test.db b/test/data/test.db index 775f2c82df3f4c7678ad4e387d553c7218578905..3305c2126f2d52e4b7ebd3a68e8398f0462f0f70 100644 GIT binary patch delta 11990 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 U#$C{j49oR6w7^MT(_G^J0K$exzW@LL delta 10130 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 diff --git a/test/test_api_usage.py b/test/test_api_usage.py index efbe5849f..5f8484448 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -250,7 +250,7 @@ class TestGetMatchingEntities(ApiTestCase): json = self.getJsonResponse('api.get_matching_entities', params=dict(prefix='o', namespace=ORGANIZATION, - includeTeams=True)) + includeTeams='true')) names = set([r['name'] for r in json['results']]) assert 'outsideorg' in names @@ -261,7 +261,7 @@ class TestGetMatchingEntities(ApiTestCase): json = self.getJsonResponse('api.get_matching_entities', params=dict(prefix='o', namespace=ORGANIZATION, - includeTeams=True)) + includeTeams='true')) names = set([r['name'] for r in json['results']]) assert 'outsideorg' in names @@ -684,12 +684,12 @@ class TestFindRepos(ApiTestCase): class TestListRepos(ApiTestCase): def test_listrepos_asguest(self): - json = self.getJsonResponse('api.list_repos', params=dict(public=True)) - assert len(json['repositories']) == 0 + json = self.getJsonResponse('api.list_repos', params=dict(public='true')) + assert len(json['repositories']) > 1 def test_listrepos_orgmember(self): self.login(READ_ACCESS_USER) - json = self.getJsonResponse('api.list_repos', params=dict(public=True)) + json = self.getJsonResponse('api.list_repos', params=dict(public='true')) assert len(json['repositories']) > 1 def test_listrepos_filter(self): From 509ba2f4f79b0d3e93bbe3161559fbae1ca4b305 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Feb 2014 17:36:58 -0500 Subject: [PATCH 2/7] Add ability to download the build pack --- static/js/controllers.js | 6 ++++++ static/partials/build-package.html | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/static/js/controllers.js b/static/js/controllers.js index 13a9a672d..0935611e7 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -793,6 +793,11 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc }, 10); }; + $scope.downloadForUser = function() { + var blob = $scope.zip.generate({type:"blob"}); + saveAs(blob, $scope.repobuild['display_name'] + '.zip'); + }; + var processBuildPack = function(response) { // Try to load as a zip file. var zipFiles = null; @@ -813,6 +818,7 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc } // Build the zip file tree. + $scope.zip = zip; $scope.tree = new FileTree(Object.keys(zipFiles)); $($scope.tree).bind('fileClicked', function(e) { var file = zip.file(e.path); diff --git a/static/partials/build-package.html b/static/partials/build-package.html index 69091167d..6222ee2ef 100644 --- a/static/partials/build-package.html +++ b/static/partials/build-package.html @@ -9,6 +9,11 @@ +
+ + + +
From d1922c6fd2f96f38a72dbcaa908e4a633eaa525d Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Feb 2014 18:31:45 -0500 Subject: [PATCH 3/7] Have the docker file view in the build pack tab use a nice formatter --- static/css/quay.css | 89 +++++++++++++++++----- static/directives/build-log-command.html | 7 +- static/directives/dockerfile-command.html | 6 ++ static/directives/dockerfile-view.html | 15 ++++ static/js/app.js | 91 +++++++++++++++++------ static/partials/build-package.html | 6 +- 6 files changed, 163 insertions(+), 51 deletions(-) create mode 100644 static/directives/dockerfile-command.html create mode 100644 static/directives/dockerfile-view.html diff --git a/static/css/quay.css b/static/css/quay.css index 73a0121e7..29620b87c 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -9,6 +9,47 @@ } } +.dockerfile-view { + margin: 20px; + padding: 20px; + background: #F7F6F6; + border: 1px solid #ddd; +} + +.dockerfile-view .entry { + font-family: Consolas, "Lucida Console", Monaco, monospace; +} + +.dockerfile-view .entry.comment { + color: rgb(82, 172, 82); +} + +.dockerfile-command { + position: relative; +} + +.dockerfile-command .command-title { + font-family: Consolas, "Lucida Console", Monaco, monospace; + padding-left: 90px; +} + +.dockerfile-command .label { + color: white; + + padding-top: 4px; + text-align: right; + margin-right: 4px; + width: 86px; + display: inline-block; + + border-right: 4px solid #aaa; + background-color: #333; + + position: absolute; + top: 0px; + left: -4px; +} + .codetooltipcontainer .tooltip-inner { white-space:pre; max-width:none; @@ -1819,6 +1860,14 @@ p.editable:hover i { left: 24px; } +.repo-build .build-pane .build-logs .dockerfile-command { + position: inherit; +} + +.repo-build .build-pane .build-logs .dockerfile-command .command-title { + padding-left: 0px; +} + .repo-build .build-pane .build-logs .container-header .container-content { display: block; padding-left: 20px; @@ -1828,26 +1877,6 @@ p.editable:hover i { padding-left: 120px; } -.label.FROM { - border-color: #5bc0de !important; -} - -.label.CMD, .label.EXPOSE, .label.ENTRYPOINT { - border-color: #428bca !important; -} - -.label.RUN, .label.ADD { - border-color: #5cb85c !important; -} - -.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR { - border-color: #f0ad4e !important; -} - -.label.MAINTAINER { - border-color: #aaa !important; -} - .repo-build .build-pane .build-logs .log-entry { position: relative; } @@ -3191,4 +3220,24 @@ pre.command:before { .file-drop { padding: 10px; margin: 10px; +} + +.label.FROM { + border-color: #5bc0de !important; +} + +.label.CMD, .label.EXPOSE, .label.ENTRYPOINT { + border-color: #428bca !important; +} + +.label.RUN, .label.ADD { + border-color: #5cb85c !important; +} + +.label.ENV, .label.VOLUME, .label.USER, .label.WORKDIR { + border-color: #f0ad4e !important; +} + +.label.MAINTAINER { + border-color: #aaa !important; } \ No newline at end of file diff --git a/static/directives/build-log-command.html b/static/directives/build-log-command.html index 211667ee4..40d1c8880 100644 --- a/static/directives/build-log-command.html +++ b/static/directives/build-log-command.html @@ -1,6 +1 @@ - - - - - + diff --git a/static/directives/dockerfile-command.html b/static/directives/dockerfile-command.html new file mode 100644 index 000000000..0e0c7bc5e --- /dev/null +++ b/static/directives/dockerfile-command.html @@ -0,0 +1,6 @@ + + + + + diff --git a/static/directives/dockerfile-view.html b/static/directives/dockerfile-view.html new file mode 100644 index 000000000..d1cb834d6 --- /dev/null +++ b/static/directives/dockerfile-view.html @@ -0,0 +1,15 @@ +
+
+
+
+
+
+
+ {{ line.text || ' ' }} +
+
+ {{ line.text || ' ' }} +
+
+
+
diff --git a/static/js/app.js b/static/js/app.js index 359212e95..cdb939745 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -2537,17 +2537,42 @@ quayApp.directive('buildLogCommand', function () { scope: { 'command': '=command' }, + controller: function($scope, $element) { + $scope.getWithoutStep = function(fullTitle) { + var colon = fullTitle.indexOf(':'); + if (colon <= 0) { + return ''; + } + + return $.trim(fullTitle.substring(colon + 1)); + }; + } + }; + return directiveDefinitionObject; +}); + + +quayApp.directive('dockerfileCommand', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/dockerfile-command.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'command': '=command' + }, controller: function($scope, $element, $sanitize) { var registryHandlers = { 'quay.io': function(pieces) { var rnamespace = pieces[pieces.length - 2]; - var rname = pieces[pieces.length - 1]; + var rname = pieces[pieces.length - 1].split(':')[0]; return '/repository/' + rnamespace + '/' + rname + '/'; }, '': function(pieces) { var rnamespace = pieces.length == 1 ? '_' : pieces[0]; - var rname = pieces[pieces.length - 1]; + var rname = pieces[pieces.length - 1].split(':')[0]; return 'https://index.docker.io/u/' + rnamespace + '/' + rname + '/'; } }; @@ -2564,25 +2589,18 @@ quayApp.directive('buildLogCommand', function () { } }; - $scope.getCommandKind = function(fullTitle) { - var colon = fullTitle.indexOf(':'); - var title = getTitleWithoutStep(fullTitle); - if (!title) { - return null; - } - + $scope.getCommandKind = function(title) { var space = title.indexOf(' '); return title.substring(0, space); }; - $scope.getCommandTitleHtml = function(fullTitle) { - var title = getTitleWithoutStep(fullTitle) || fullTitle; + $scope.getCommandTitleHtml = function(title) { var space = title.indexOf(' '); if (space <= 0) { return $sanitize(title); } - var kind = $scope.getCommandKind(fullTitle); + var kind = $scope.getCommandKind(title); var sanitized = $sanitize(title.substring(space + 1)); var handler = kindHandlers[kind || '']; @@ -2591,21 +2609,50 @@ quayApp.directive('buildLogCommand', function () { } else { return sanitized; } - }; - - var getTitleWithoutStep = function(fullTitle) { - var colon = fullTitle.indexOf(':'); - if (colon <= 0) { - return null; - } - - return $.trim(fullTitle.substring(colon + 1)); - }; + }; } }; return directiveDefinitionObject; }); + +quayApp.directive('dockerfileView', function () { + var directiveDefinitionObject = { + priority: 0, + templateUrl: '/static/directives/dockerfile-view.html', + replace: false, + transclude: false, + restrict: 'C', + scope: { + 'contents': '=contents' + }, + controller: function($scope, $element, $sanitize) { + $scope.$watch('contents', function(contents) { + $scope.lines = []; + + var lines = contents ? contents.split('\n') : []; + for (var i = 0; i < lines.length; ++i) { + var line = $.trim(lines[i]); + var kind = 'text'; + if (line && line[0] == '#') { + kind = 'comment'; + } else if (line.match(/^([A-Z]+\s)/)) { + kind = 'command'; + } + + var lineInfo = { + 'text': $sanitize(line), + 'kind': kind + }; + $scope.lines.push(lineInfo); + } + }); + } + }; + return directiveDefinitionObject; +}); + + quayApp.directive('buildStatus', function () { var directiveDefinitionObject = { priority: 0, diff --git a/static/partials/build-package.html b/static/partials/build-package.html index 6222ee2ef..182fd9c58 100644 --- a/static/partials/build-package.html +++ b/static/partials/build-package.html @@ -10,8 +10,8 @@
@@ -37,7 +37,7 @@
-
{{ dockerFileContents }}
+
No Dockerfile found in the build pack
From 0e566b63cf02e375c174ab2830c926df9ae1a44c Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 17 Feb 2014 21:21:55 -0500 Subject: [PATCH 4/7] Fix resize issue on build pack tree --- static/js/controllers.js | 5 ++++- static/js/graphing.js | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/static/js/controllers.js b/static/js/controllers.js index 0935611e7..6b7599d59 100644 --- a/static/js/controllers.js +++ b/static/js/controllers.js @@ -785,7 +785,10 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc }; $scope.initializeTree = function() { - if ($scope.drawn) { return; } + if ($scope.drawn) { + $scope.tree.notifyResized(); + return; + } $scope.drawn = true; $timeout(function() { diff --git a/static/js/graphing.js b/static/js/graphing.js index d73844ceb..b796fd8a4 100644 --- a/static/js/graphing.js +++ b/static/js/graphing.js @@ -857,10 +857,10 @@ FileTreeBase.prototype.calculateDimensions_ = function(container) { var margin = { top: 40, right: 00, bottom: 20, left: 20 }; var m = [margin.top, margin.right, margin.bottom, margin.left]; - var w = cw - m[1] - m[3]; - var h = ch - m[0] - m[2]; + var w = Math.max(0, cw - m[1] - m[3]); + var h = Math.max(0, ch - m[0] - m[2]); - var barWidth = cw * 0.8 - m[1] - m[3]; + var barWidth = Math.max(0, cw * 0.8 - m[1] - m[3]); return { 'w': w, From e921222860615f99250cda5f686eea6015cf8523 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 18 Feb 2014 01:19:09 -0500 Subject: [PATCH 5/7] Fix footer in firefox in certain cases --- static/css/quay.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/quay.css b/static/css/quay.css index 29620b87c..0bb108ed3 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -305,7 +305,7 @@ i.toggle-icon:hover { } .footer-container, .push { - height: 74px; + height: 100px; } .footer-container.fixed { From 6e2d1dc60e704933cf2a2c596083d871e7fb2a67 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 18 Feb 2014 14:46:56 -0500 Subject: [PATCH 6/7] Turn off the background circle on the repo icon in view repo --- static/partials/view-repo.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/partials/view-repo.html b/static/partials/view-repo.html index b3ce4a51c..0cde31993 100644 --- a/static/partials/view-repo.html +++ b/static/partials/view-repo.html @@ -9,7 +9,7 @@

- +

From fe9d628a5c65f2a09c5438febb401e6bf2e291a4 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 18 Feb 2014 15:04:39 -0500 Subject: [PATCH 7/7] Restyle the github login page --- static/partials/user-admin.html | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/static/partials/user-admin.html b/static/partials/user-admin.html index 347b0f21c..ffc70257c 100644 --- a/static/partials/user-admin.html +++ b/static/partials/user-admin.html @@ -62,7 +62,7 @@
Account e-mail address
-
+
{{ user.email }}
@@ -113,14 +113,11 @@
-
GitHub Login
+
GitHub Login:
- - - - - This account is connected with GitHub account: {{githubLogin}} + + {{githubLogin}}