Merge pull request #3092 from quay/joseph.schorr/QUAY-949/image-tracks

Image track improvements
This commit is contained in:
Joseph Schorr 2018-05-23 16:25:48 -04:00 committed by GitHub
commit 86929c16d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 194 additions and 60 deletions

View file

@ -28,6 +28,22 @@
cursor: pointer; 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 { .repo-panel-tags-element .image-track-line {
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -208,4 +224,38 @@
.repo-panel-tags-element .disabled-option, .repo-panel-tags-element .disabled-option,
.repo-panel-tags-element .disabled-option a { .repo-panel-tags-element .disabled-option a {
color: #ccc; 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;
} }

View file

@ -0,0 +1,11 @@
<div class="popover image-tag-tooltip" tabindex="-1">
<div class="image-tag-tooltip-header"
ng-style="::{'backgroundColor': trackEntryForImage[tag.image_id].color,
'color': constrastingColor( trackEntryForImage[tag.image_id].color)}">
Image {{ tag.image_id.substr(0, 12) }}
</div>
<ul class="image-tag-tooltip-tags">
<li ng-repeat="tag in imageMap[tag.image_id] | limitTo:5"><i class="fa fa-tag"></i>{{ tag.name }}</li>
</ul>
<div class="image-tag-tooltip-tags-more" ng-if="imageMap[tag.image_id].length > 5">and {{ imageMap[tag.image_id].length - 5 }} more tags</div>
</div>

View file

@ -33,8 +33,8 @@
</div> </div>
<div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, item)" <div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, item)"
ng-repeat="it in imageTrackEntries"> ng-repeat="it in imageTrackEntries" ng-if="::it.visible">
<i class="fa fa-circle-o" ng-style="{'color': it.color}"></i> {{ it.image_id.substr(0, 12) }} <i class="fa fa-circle-o" ng-style="::{'color': it.color}"></i> {{ ::it.image_id.substr(0, 12) }}
</div> </div>
</span> </span>
@ -116,6 +116,9 @@
style="width: 140px;"> style="width: 140px;">
<a ng-click="orderBy('expiration_date')" data-title="When the tag expires" data-container="body" bs-tooltip>Expires</a> <a ng-click="orderBy('expiration_date')" data-title="When the tag expires" data-container="body" bs-tooltip>Expires</a>
</td> </td>
<td class="hidden-xs hidden-sm" ng-if="imageTracks.length > maxTrackCount"
style="width: 20px; position: relative;">
</td>
<td class="hidden-xs hidden-sm" <td class="hidden-xs hidden-sm"
ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)" ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
style="width: 140px;"> style="width: 140px;">
@ -123,10 +126,6 @@
</td> </td>
<td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks" <td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks"
ng-if="imageTracks.length <= maxTrackCount"></td> ng-if="imageTracks.length <= maxTrackCount"></td>
<td class="hidden-xs hidden-sm" ng-if="imageTracks.length > maxTrackCount"
style="width: 20px; position: relative;">
<span class="image-track-dot" style="border-color: #ccc; top: 4px;"></span>
</td>
<td class="options-col"></td> <td class="options-col"></td>
<td class="options-col"></td> <td class="options-col"></td>
<td class="hidden-xs hidden-sm" style="width: 4px"></td> <td class="hidden-xs hidden-sm" style="width: 4px"></td>
@ -246,26 +245,38 @@
</td> </td>
<!-- Manifest link --> <!-- Manifest link -->
<td class="hidden-xs hidden-sm hidden-md image-track"
ng-if="imageTracks.length > maxTrackCount">
<span class="image-track-filled-dot"
ng-if="::trackEntryForImage[tag.image_id]"
ng-style="::{'backgroundColor': trackEntryForImage[tag.image_id].color}"
ng-click="::selectTrack(trackEntryForImage[tag.image_id])"
data-template-url="/static/directives/repo-view/image-tag-tooltip.html"
data-placement="left"
data-trigger="hover"
data-animation="am-flip-x"
data-auto-close="1"
bs-popover></span>
</td>
<td class="hidden-xs hidden-sm image-id-col"> <td class="hidden-xs hidden-sm image-id-col">
<manifest-link repository="repository" image-id="tag.image_id" manifest-digest="tag.manifest_digest"></manifest-link> <manifest-link repository="repository" image-id="tag.image_id" manifest-digest="tag.manifest_digest"></manifest-link>
</td> </td>
<td class="hidden-xs hidden-sm hidden-md image-track"
ng-if="imageTracks.length > maxTrackCount" bindonce>
<span ng-repeat="it in imageTracks">
<span ng-repeat="entry in it.entries">
<span class="image-track-dot" bo-if="entry.image_id == tag.image_id"
bo-style="{'borderColor': entry.color}" ng-click="selectTrack(entry)"></span>
</span>
</span>
</td>
<td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks" <td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks"
ng-if="imageTracks.length <= maxTrackCount" bindonce> ng-if="imageTracks.length <= maxTrackCount">
<span ng-repeat="entry in it.entries"> <span class="image-track-dot"
<span class="image-track-dot" bo-if="entry.image_id == tag.image_id" ng-if="::it.entryByImageId[tag.image_id]"
bo-style="{'borderColor': entry.color}" ng-click="selectTrack(entry)"></span> ng-style="::{'borderColor': trackEntryForImage[tag.image_id].color}"
<span class="image-track-line" bo-class="trackLineClass($parent.$parent.$parent.$index, entry)" ng-click="::selectTrack(trackEntryForImage[tag.image_id])"
bo-style="{'borderColor': entry.color}"></span> data-template-url="/static/directives/repo-view/image-tag-tooltip.html"
</span> data-placement="left"
data-trigger="hover"
data-animation="am-flip-x"
data-auto-close="1"
bs-popover></span>
<span class="image-track-line"
ng-if="::getTrackEntryForIndex(it, $parent.$parent.$index)"
ng-class="::trackLineClass(it, $parent.$parent.$parent.$index)"
ng-style="::{'borderColor': getTrackEntryForIndex(it, $parent.$parent.$parent.$index).color}"></span>
</td> </td>
<td class="options-col"> <td class="options-col">
<i class="fa fa-download" data-title="Fetch Tag" bs-tooltip <i class="fa fa-download" data-title="Fetch Tag" bs-tooltip
@ -315,11 +326,10 @@
</td> </td>
<td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks" <td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks"
ng-if="imageTracks.length <= maxTrackCount" bindonce> ng-if="imageTracks.length <= maxTrackCount" bindonce>
<span ng-repeat="entry in it.entries"> <span class="image-track-line"
<span class="image-track-line" ng-if="::getTrackEntryForIndex(it, $parent.$parent.$index)"
bo-class="trackLineExpandedClass($parent.$parent.$parent.$index, entry)" ng-class="::trackLineExpandedClass(it, $parent.$parent.$parent.$index)"
bo-style="{'borderColor': entry.color}"></span> ng-style="::{'borderColor': getTrackEntryForIndex(it, $parent.$parent.$parent.$index).color}"></span>
</span>
</td> </td>
<td colspan="1" class="hidden-xs hidden-sm hidden-md"></td> <td colspan="1" class="hidden-xs hidden-sm hidden-md"></td>
</tr> </tr>

View file

@ -110,24 +110,33 @@ angular.module('quay').directive('repoPanelTags', function () {
// Calculate the image tracks. // Calculate the image tracks.
var colors = d3.scale.category10(); var colors = d3.scale.category10();
if (Object.keys(imageMap).length > 10) {
colors = d3.scale.category20();
}
var imageTracks = []; var imageTracks = [];
var imageTrackEntries = []; 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) { imageIDs.sort().map(function(image_id) {
if (imageMap[image_id].length >= 2){ if (imageMap[image_id].length >= 2){
// Create the track entry. // Create the track entry.
var index = imageTrackEntries.length; var imageIndexRange = imageIndexMap[image_id];
var colorIndex = imageTrackEntries.length;
var trackEntry = { var trackEntry = {
'image_id': image_id, 'image_id': image_id,
'color': colors(index), 'color': colors(colorIndex),
'count': imageMap[image_id].length, '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); trackEntryForImage[image_id] = trackEntry;
imageMap[image_id]['color'] = colors(colorIndex);
imageMap[image_id]['color'] = colors(index);
var currentImageIndex = imageIndexMap[image_id];
// Find the track in which we can place the entry, if any. // Find the track in which we can place the entry, if any.
var existingTrack = null; var existingTrack = null;
@ -140,7 +149,7 @@ angular.module('quay').directive('repoPanelTags', function () {
for (var j = 0; j < currentTrack.entries.length; ++j) { for (var j = 0; j < currentTrack.entries.length; ++j) {
var currentTrackEntry = currentTrack.entries[j]; var currentTrackEntry = currentTrack.entries[j];
var entryInfo = imageIndexMap[currentTrackEntry.image_id]; 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; canAddToCurrentTrack = false;
break; break;
} }
@ -155,17 +164,38 @@ angular.module('quay').directive('repoPanelTags', function () {
// Add the entry to the track or create a new track if necessary. // Add the entry to the track or create a new track if necessary.
if (existingTrack) { if (existingTrack) {
existingTrack.entries.push(trackEntry) 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 { } else {
var entryByImageId = {};
entryByImageId[image_id] = trackEntry;
var entryByIndex = {};
for (var j = imageIndexRange.start; j <= imageIndexRange.end; j++) {
entryByIndex[j] = trackEntry;
}
imageTracks.push({ imageTracks.push({
'entries': [trackEntry] 'entries': [trackEntry],
'entryByImageId': entryByImageId,
'startIndex': imageIndexRange.start,
'endIndex': imageIndexRange.end,
'entryByIndex': entryByIndex,
}); });
} }
imageTrackEntries.push(trackEntry);
} }
}); });
$scope.imageMap = imageMap; $scope.imageMap = imageMap;
$scope.imageTracks = imageTracks; $scope.imageTracks = imageTracks;
$scope.imageTrackEntries = imageTrackEntries; $scope.imageTrackEntries = imageTrackEntries;
$scope.trackEntryForImage = trackEntryForImage;
$scope.options.page = 0; $scope.options.page = 0;
@ -293,48 +323,77 @@ angular.module('quay').directive('repoPanelTags', function () {
$scope.checkedTags.setChecked($scope.tags); $scope.checkedTags.setChecked($scope.tags);
}; };
$scope.trackLineExpandedClass = function(index, track_info) { $scope.constrastingColor = function(backgroundColor) {
var startIndex = $.inArray(track_info.tags[0], $scope.tags); // From: https://stackoverflow.com/questions/11068240/what-is-the-most-efficient-way-to-parse-a-css-color-in-javascript
var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags); function parseColor(input) {
index += $scope.options.page * $scope.tagsPerPage; 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'; return 'before';
} }
if (index > endIndex) { if (index > entry.index_range.end) {
return 'after'; return 'after';
} }
if (index >= startIndex && index < endIndex) { if (index >= entry.index_range.start && index < entry.index_range.end) {
return 'middle'; return 'middle';
} }
return ''; return '';
}; };
$scope.trackLineClass = function(index, track_info) { $scope.trackLineClass = function(it, index) {
var startIndex = $.inArray(track_info.tags[0], $scope.tags); var entry = $scope.getTrackEntryForIndex(it, index);
var endIndex = $.inArray(track_info.tags[track_info.tags.length - 1], $scope.tags); if (!entry) {
index += $scope.options.page * $scope.tagsPerPage; return '';
}
if (index == startIndex) { var adjustedIndex = index + ($scope.options.page * $scope.tagsPerPage);
if (index == entry.index_range.start) {
return 'start'; return 'start';
} }
if (index == endIndex) { if (index == entry.index_range.end) {
return 'end'; return 'end';
} }
if (index > startIndex && index < endIndex) { if (index > entry.index_range.start && index < entry.index_range.end) {
return 'middle'; return 'middle';
} }
if (index < startIndex) { if (index < entry.index_range.start) {
return 'before'; return 'before';
} }
if (index > endIndex) { if (index > entry.index_range.end) {
return 'after'; return 'after';
} }
}; };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long