From b106a31b05dc14afd38227d8fa4711ce7ee4bddd Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 23 May 2018 16:14:27 -0400 Subject: [PATCH] Improvements for the image tracks in the tags view 1) Change to show solid dots (instead of open ones) if we are collapsing into a single track due to track count being > 5 2) Add a hover tooltip on track dots to show the tags associated with that image 3) Change the tag selection menu to only show images that are visible currently 4) Refactor the tracks code to massively reduce the amount of ng-repeats thus making the loading much faster when there are many, many tags Fixes https://jira.coreos.com/browse/QUAY-949 --- .../directives/repo-view/repo-panel-tags.css | 50 ++++++++ .../repo-view/image-tag-tooltip.html | 11 ++ .../directives/repo-view/repo-panel-tags.html | 64 +++++----- .../directives/repo-view/repo-panel-tags.js | 109 ++++++++++++++---- 4 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 static/directives/repo-view/image-tag-tooltip.html diff --git a/static/css/directives/repo-view/repo-panel-tags.css b/static/css/directives/repo-view/repo-panel-tags.css index 58872d33f..8dea9ae7e 100644 --- a/static/css/directives/repo-view/repo-panel-tags.css +++ b/static/css/directives/repo-view/repo-panel-tags.css @@ -28,6 +28,22 @@ cursor: pointer; } +.repo-panel-tags-element .image-track-filled-dot { + display: inline-block; + + position: absolute; + top: 15px; + left: 2px; + width: 12px; + height: 12px; + + background: white; + z-index: 300; + + border-radius: 50%; + cursor: pointer; +} + .repo-panel-tags-element .image-track-line { position: absolute; top: 0px; @@ -208,4 +224,38 @@ .repo-panel-tags-element .disabled-option, .repo-panel-tags-element .disabled-option a { color: #ccc; +} + +.repo-panel-tags-element .image-tag-tooltip { + padding-bottom: 4px; +} + +.repo-panel-tags-element .image-tag-tooltip .image-tag-tooltip-header { + padding: 4px; + padding-left: 10px; + padding-right: 10px; + border-radius: 4px; + margin-bottom: 10px; +} + +.repo-panel-tags-element .image-tag-tooltip .image-tag-tooltip-tags { + list-style: none; + padding: 10px; + padding-top: 0px; + padding-bottom: 0px; + margin-bottom: 4px; +} + +.repo-panel-tags-element .image-tag-tooltip .image-tag-tooltip-tags .fa-tag { + margin-right: 8px; + color: #ccc; + vertical-align: middle; +} + +.repo-panel-tags-element .image-tag-tooltip .image-tag-tooltip-tags-more { + color: #aaa; + font-size: 14px; + margin-bottom: 4px; + text-align: center; + padding: 4px; } \ No newline at end of file diff --git a/static/directives/repo-view/image-tag-tooltip.html b/static/directives/repo-view/image-tag-tooltip.html new file mode 100644 index 000000000..3b1051d06 --- /dev/null +++ b/static/directives/repo-view/image-tag-tooltip.html @@ -0,0 +1,11 @@ +
+
+ Image {{ tag.image_id.substr(0, 12) }} +
+ +
and {{ imageMap[tag.image_id].length - 5 }} more tags
+
\ No newline at end of file diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html index 081e801eb..873be00e6 100644 --- a/static/directives/repo-view/repo-panel-tags.html +++ b/static/directives/repo-view/repo-panel-tags.html @@ -33,8 +33,8 @@
- {{ it.image_id.substr(0, 12) }} + ng-repeat="it in imageTrackEntries" ng-if="::it.visible"> + {{ ::it.image_id.substr(0, 12) }}
@@ -116,6 +116,9 @@ style="width: 140px;"> Expires + + @@ -123,10 +126,6 @@ - - - @@ -246,26 +245,38 @@ + + + - - - - - - - - - - - + ng-if="imageTracks.length <= maxTrackCount"> + + - - - + diff --git a/static/js/directives/repo-view/repo-panel-tags.js b/static/js/directives/repo-view/repo-panel-tags.js index d9182eaaa..f765df037 100644 --- a/static/js/directives/repo-view/repo-panel-tags.js +++ b/static/js/directives/repo-view/repo-panel-tags.js @@ -110,24 +110,33 @@ angular.module('quay').directive('repoPanelTags', function () { // Calculate the image tracks. var colors = d3.scale.category10(); + if (Object.keys(imageMap).length > 10) { + colors = d3.scale.category20(); + } + var imageTracks = []; var imageTrackEntries = []; + var trackEntryForImage = {}; + + var visibleStartIndex = ($scope.options.page * $scope.tagsPerPage); + var visibleEndIndex = (($scope.options.page + 1) * $scope.tagsPerPage); imageIDs.sort().map(function(image_id) { if (imageMap[image_id].length >= 2){ // Create the track entry. - var index = imageTrackEntries.length; + var imageIndexRange = imageIndexMap[image_id]; + var colorIndex = imageTrackEntries.length; var trackEntry = { 'image_id': image_id, - 'color': colors(index), + 'color': colors(colorIndex), 'count': imageMap[image_id].length, - 'tags': imageMap[image_id] + 'tags': imageMap[image_id], + 'index_range': imageIndexRange, + 'visible': visibleStartIndex <= imageIndexRange.end && imageIndexRange.start <= visibleEndIndex, }; - imageTrackEntries.push(trackEntry); - - imageMap[image_id]['color'] = colors(index); - var currentImageIndex = imageIndexMap[image_id]; + trackEntryForImage[image_id] = trackEntry; + imageMap[image_id]['color'] = colors(colorIndex); // Find the track in which we can place the entry, if any. var existingTrack = null; @@ -140,7 +149,7 @@ angular.module('quay').directive('repoPanelTags', function () { for (var j = 0; j < currentTrack.entries.length; ++j) { var currentTrackEntry = currentTrack.entries[j]; var entryInfo = imageIndexMap[currentTrackEntry.image_id]; - if (Math.max(entryInfo.start, currentImageIndex.start) <= Math.min(entryInfo.end, currentImageIndex.end)) { + if (Math.max(entryInfo.start, imageIndexRange.start) <= Math.min(entryInfo.end, imageIndexRange.end)) { canAddToCurrentTrack = false; break; } @@ -155,17 +164,38 @@ angular.module('quay').directive('repoPanelTags', function () { // Add the entry to the track or create a new track if necessary. if (existingTrack) { existingTrack.entries.push(trackEntry) + existingTrack.entryByImageId[image_id] = trackEntry; + existingTrack.endIndex = Math.max(existingTrack.endIndex, imageIndexRange.end); + + for (var j = imageIndexRange.start; j <= imageIndexRange.end; j++) { + existingTrack.entryByIndex[j] = trackEntry; + } } else { + var entryByImageId = {}; + entryByImageId[image_id] = trackEntry; + + var entryByIndex = {}; + for (var j = imageIndexRange.start; j <= imageIndexRange.end; j++) { + entryByIndex[j] = trackEntry; + } + imageTracks.push({ - 'entries': [trackEntry] + 'entries': [trackEntry], + 'entryByImageId': entryByImageId, + 'startIndex': imageIndexRange.start, + 'endIndex': imageIndexRange.end, + 'entryByIndex': entryByIndex, }); } + + imageTrackEntries.push(trackEntry); } }); $scope.imageMap = imageMap; $scope.imageTracks = imageTracks; $scope.imageTrackEntries = imageTrackEntries; + $scope.trackEntryForImage = trackEntryForImage; $scope.options.page = 0; @@ -293,48 +323,77 @@ angular.module('quay').directive('repoPanelTags', function () { $scope.checkedTags.setChecked($scope.tags); }; - $scope.trackLineExpandedClass = function(index, track_info) { - var startIndex = $.inArray(track_info.tags[0], $scope.tags); - var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags); - index += $scope.options.page * $scope.tagsPerPage; + $scope.constrastingColor = function(backgroundColor) { + // From: https://stackoverflow.com/questions/11068240/what-is-the-most-efficient-way-to-parse-a-css-color-in-javascript + function parseColor(input) { + m = input.match(/^#([0-9a-f]{6})$/i)[1]; + return [ + parseInt(m.substr(0,2),16), + parseInt(m.substr(2,2),16), + parseInt(m.substr(4,2),16) + ]; + } - if (index < startIndex) { + var rgb = parseColor(backgroundColor); + + // From W3C standard. + var o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000); + return (o > 150) ? 'black' : 'white'; + }; + + $scope.getTrackEntryForIndex = function(it, index) { + index += $scope.options.page * $scope.tagsPerPage; + return it.entryByIndex[index]; + }; + + $scope.trackLineExpandedClass = function(it, index, track_info) { + var entry = $scope.getTrackEntryForIndex(it, index); + if (!entry) { + return ''; + } + + var adjustedIndex = index + ($scope.options.page * $scope.tagsPerPage); + + if (index < entry.index_range.start) { return 'before'; } - if (index > endIndex) { + if (index > entry.index_range.end) { return 'after'; } - if (index >= startIndex && index < endIndex) { + if (index >= entry.index_range.start && index < entry.index_range.end) { return 'middle'; } return ''; }; - $scope.trackLineClass = function(index, track_info) { - var startIndex = $.inArray(track_info.tags[0], $scope.tags); - var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags); - index += $scope.options.page * $scope.tagsPerPage; + $scope.trackLineClass = function(it, index) { + var entry = $scope.getTrackEntryForIndex(it, index); + if (!entry) { + return ''; + } - if (index == startIndex) { + var adjustedIndex = index + ($scope.options.page * $scope.tagsPerPage); + + if (index == entry.index_range.start) { return 'start'; } - if (index == endIndex) { + if (index == entry.index_range.end) { return 'end'; } - if (index > startIndex && index < endIndex) { + if (index > entry.index_range.start && index < entry.index_range.end) { return 'middle'; } - if (index < startIndex) { + if (index < entry.index_range.start) { return 'before'; } - if (index > endIndex) { + if (index > entry.index_range.end) { return 'after'; } };