Merge pull request #3092 from quay/joseph.schorr/QUAY-949/image-tracks
Image track improvements
This commit is contained in:
commit
86929c16d3
7 changed files with 194 additions and 60 deletions
|
@ -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;
|
||||
}
|
11
static/directives/repo-view/image-tag-tooltip.html
Normal file
11
static/directives/repo-view/image-tag-tooltip.html
Normal 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>
|
|
@ -33,8 +33,8 @@
|
|||
</div>
|
||||
|
||||
<div class="cor-checkable-menu-item" item-filter="imageIDFilter(it.image_id, item)"
|
||||
ng-repeat="it in imageTrackEntries">
|
||||
<i class="fa fa-circle-o" ng-style="{'color': it.color}"></i> {{ it.image_id.substr(0, 12) }}
|
||||
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) }}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
|
@ -116,6 +116,9 @@
|
|||
style="width: 140px;">
|
||||
<a ng-click="orderBy('expiration_date')" data-title="When the tag expires" data-container="body" bs-tooltip>Expires</a>
|
||||
</td>
|
||||
<td class="hidden-xs hidden-sm" ng-if="imageTracks.length > maxTrackCount"
|
||||
style="width: 20px; position: relative;">
|
||||
</td>
|
||||
<td class="hidden-xs hidden-sm"
|
||||
ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
|
||||
style="width: 140px;">
|
||||
|
@ -123,10 +126,6 @@
|
|||
</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks"
|
||||
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="hidden-xs hidden-sm" style="width: 4px"></td>
|
||||
|
@ -246,26 +245,38 @@
|
|||
</td>
|
||||
|
||||
<!-- 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">
|
||||
<manifest-link repository="repository" image-id="tag.image_id" manifest-digest="tag.manifest_digest"></manifest-link>
|
||||
</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"
|
||||
ng-if="imageTracks.length <= maxTrackCount" bindonce>
|
||||
<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 class="image-track-line" bo-class="trackLineClass($parent.$parent.$parent.$index, entry)"
|
||||
bo-style="{'borderColor': entry.color}"></span>
|
||||
</span>
|
||||
ng-if="imageTracks.length <= maxTrackCount">
|
||||
<span class="image-track-dot"
|
||||
ng-if="::it.entryByImageId[tag.image_id]"
|
||||
ng-style="::{'borderColor': 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>
|
||||
<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 class="options-col">
|
||||
<i class="fa fa-download" data-title="Fetch Tag" bs-tooltip
|
||||
|
@ -315,11 +326,10 @@
|
|||
</td>
|
||||
<td class="hidden-xs hidden-sm hidden-md image-track" ng-repeat="it in imageTracks"
|
||||
ng-if="imageTracks.length <= maxTrackCount" bindonce>
|
||||
<span ng-repeat="entry in it.entries">
|
||||
<span class="image-track-line"
|
||||
bo-class="trackLineExpandedClass($parent.$parent.$parent.$index, entry)"
|
||||
bo-style="{'borderColor': entry.color}"></span>
|
||||
</span>
|
||||
<span class="image-track-line"
|
||||
ng-if="::getTrackEntryForIndex(it, $parent.$parent.$index)"
|
||||
ng-class="::trackLineExpandedClass(it, $parent.$parent.$parent.$index)"
|
||||
ng-style="::{'borderColor': getTrackEntryForIndex(it, $parent.$parent.$parent.$index).color}"></span>
|
||||
</td>
|
||||
<td colspan="1" class="hidden-xs hidden-sm hidden-md"></td>
|
||||
</tr>
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
};
|
||||
|
|
3
static/lib/angular-strap.compat.min.js
vendored
Normal file
3
static/lib/angular-strap.compat.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
static/lib/angular-strap.min.js
vendored
11
static/lib/angular-strap.min.js
vendored
File diff suppressed because one or more lines are too long
6
static/lib/angular-strap.tpl.min.js
vendored
6
static/lib/angular-strap.tpl.min.js
vendored
File diff suppressed because one or more lines are too long
Reference in a new issue