/** * An element which displays the changes visualization panel for a repository view. */ angular.module('quay').directive('repoPanelChanges', function () { var RepositoryImageTracker = function(repository, imageLoader) { this.repository = repository; this.imageLoader = imageLoader; // Build a map of image ID -> image. var images = imageLoader.images; var imageIDMap = {}; images.forEach(function(image) { imageIDMap[image.id] = image; }); this.imageMap_ = imageIDMap; }; RepositoryImageTracker.prototype.imageLink = function(image) { return '/repository/' + this.repository.namespace + '/' + this.repository.name + '/image/' + image; }; RepositoryImageTracker.prototype.getImageForTag = function(tag) { var tagData = this.lookupTag(tag); if (!tagData) { return null; } return this.imageMap_[tagData.image_id]; }; RepositoryImageTracker.prototype.lookupTag = function(tag) { return this.repository.tags[tag]; }; RepositoryImageTracker.prototype.lookupImage = function(image) { return this.imageMap_[image]; }; RepositoryImageTracker.prototype.forAllTagImages = function(tag, callback) { var tagData = this.lookupTag(tag); if (!tagData) { return; } var tagImage = this.imageMap_[tagData.image_id]; if (!tagImage) { return; } // Callback the tag's image itself. callback(tagImage); // Callback any parent images. if (!tagImage.ancestors) { return; } var ancestors = tagImage.ancestors.split('/'); for (var i = 0; i < ancestors.length; ++i) { var image = this.imageMap_[ancestors[i]]; if (image) { callback(image); } } }; RepositoryImageTracker.prototype.getTotalSize = function(tag) { var size = 0; this.forAllTagImages(tag, function(image) { size += image.size; }); return size; }; RepositoryImageTracker.prototype.getImagesForTagBySize = function(tag) { var images = []; this.forAllTagImages(tag, function(image) { images.push(image); }); images.sort(function(a, b) { return b.size - a.size; }); return images; }; /////////////////////////////////////////////////////////////////////////////////////// var directiveDefinitionObject = { priority: 0, templateUrl: '/static/directives/repo-view/repo-panel-changes.html', replace: false, transclude: false, restrict: 'C', scope: { 'repository': '=repository', 'selectedTags': '=selectedTags', 'imagesResource': '=imagesResource', 'imageLoader': '=imageLoader', 'isEnabled': '=isEnabled' }, controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) { $scope.tagNames = []; $scope.loading = true; $scope.$watch('selectedTags', function(selectedTags) { if (!selectedTags) { return; } $scope.selectedTagsSlice = selectedTags.slice(0, 10); }); var update = function() { if (!$scope.repository || !$scope.isEnabled) { return; } $scope.tagNames = Object.keys($scope.repository.tags); $scope.currentImage = null; $scope.currentTag = null; $scope.loading = true; $scope.imageLoader.loadImages($scope.selectedTagsSlice, function() { $scope.loading = false; updateImages(); }); }; var updateImages = function() { if (!$scope.repository || !$scope.imageLoader || !$scope.isEnabled) { return; } $scope.tracker = new RepositoryImageTracker($scope.repository, $scope.imageLoader); if ($scope.selectedTagsSlice && $scope.selectedTagsSlice.length) { refreshTree(); } }; $scope.$watch('selectedTagsSlice', update) $scope.$watch('repository', update); $scope.$watch('isEnabled', update); $scope.updateState = function() { update(); }; var refreshTree = function() { if (!$scope.repository || !$scope.imageLoader || !$scope.isEnabled) { return; } if ($scope.selectedTagsSlice.length < 1) { return; } $('#image-history-container').empty(); var getTagsForImage = function(image) { return $scope.imageLoader.getTagsForImage(image); }; var tree = new ImageHistoryTree( $scope.repository.namespace, $scope.repository.name, $scope.imageLoader.images, getTagsForImage, UtilService.getFirstMarkdownLineAsText, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand, function(tag) { return $.inArray(tag, $scope.selectedTagsSlice) >= 0; }); $scope.tree = tree.draw('image-history-container'); if ($scope.tree) { // Give enough time for the UI to be drawn before we resize the tree. $timeout(function() { $scope.tree.notifyResized(); $scope.setTag($scope.selectedTagsSlice[0]); }, 100); // Listen for changes to the selected tag and image in the tree. $($scope.tree).bind('tagChanged', function(e) { $scope.$apply(function() { $scope.setTag(e.tag); }); }); $($scope.tree).bind('imageChanged', function(e) { $scope.$apply(function() { $scope.setImage(e.image.id); }); }); } }; $scope.setImage = function(image_id) { $scope.currentTag = null; $scope.currentImage = image_id; $scope.tree.setImage(image_id); }; $scope.setTag = function(tag) { $scope.currentTag = tag; $scope.currentImage = null; $scope.tree.setTag(tag); }; $scope.parseDate = function(dateString) { return Date.parse(dateString); }; $scope.getTimeSince = function(createdTime) { return moment($scope.parseDate(createdTime)).fromNow(); }; $scope.handleTagChanged = function(data) { data.removed.map(function(tag) { $scope.currentImage = null; $scope.currentTag = null; }); data.added.map(function(tag) { $scope.selectedTags.push(tag); $scope.currentTag = tag; }); update(); }; } }; return directiveDefinitionObject; });