Switch avatars to be built out of CSS and only overlayed with the gravatar when a non-default exists

This commit is contained in:
Joseph Schorr 2015-03-30 17:55:04 -04:00
parent 2d8d0c6fd3
commit 27a9b84587
94 changed files with 663 additions and 303 deletions

View file

@ -0,0 +1,23 @@
.avatar-element {
display: inline-block;
vertical-align: middle;
color: white !important;
text-align: center;
position: relative;
background: white;
overflow: hidden;
}
.avatar-element.team {
border-radius: 50%;
}
.avatar-element img {
position: absolute;
top: 0px;
left: 0px;
}
.avatar-element .letter {
cursor: default !important;
}

View file

@ -0,0 +1,7 @@
.entity-reference .new-entity-reference .entity-name {
margin-left: 6px;
}
.entity-reference .new-entity-reference .fa-wrench {
width: 16px;
}

View file

@ -0,0 +1,52 @@
.entity-search-element {
position: relative;
display: block;
}
.entity-search-element .entity-reference {
position: absolute !important;
top: 7px;
left: 8px;
right: 36px;
z-index: 0;
pointer-events: none;
}
.entity-search-element .entity-reference .entity-reference-element {
pointer-events: none;
}
.entity-search-element .entity-reference-element i.fa-exclamation-triangle {
pointer-events: all;
}
.entity-search-element .entity-reference .entity-name {
display: none;
}
.entity-search-element input {
vertical-align: middle;
width: 100%;
}
.entity-search-element.persistent input {
padding-left: 28px;
padding-right: 28px;
}
.entity-search-element .twitter-typeahead {
vertical-align: middle;
display: block !important;
margin-right: 36px;
}
.entity-search-element .dropdown {
vertical-align: middle;
position: absolute;
top: 0px;
right: 0px;
}
.entity-search-element .menuitem .avatar {
margin-right: 4px;
}

View file

@ -1,3 +1,55 @@
.teams-manager .popup-input-button {
float: right;
}
}
.teams-manager .manager-header {
border-bottom: 1px solid #eee;
margin-bottom: 10px;
}
.teams-manager .cor-options-menu {
display: inline-block;
margin-left: 10px;
}
.teams-manager .header-col .info-icon {
font-size: 16px;
}
.teams-manager .header-col .header-text {
text-transform: uppercase;
font-size: 14px;
color: #aaa !important;
display: inline-block;
padding-top: 4px;
}
.teams-manager .control-col {
padding-top: 6px;
}
.teams-manager .team-listing {
margin-bottom: 10px;
}
.teams-manager .team-listing .avatar {
margin-right: 6px;
}
.teams-manager .team-member-list .fa {
color: #ccc;
}
.teams-manager .team-member-list {
position: relative;
min-height: 20px;
padding: 4px;
padding-left: 40px;
}
.teams-manager .team-member-list .team-member-more {
vertical-align: middle;
padding-left: 6px;
color: #aaa;
font-size: 14px;
}

View file

@ -372,55 +372,6 @@ nav.navbar-default .navbar-nav>li>a.active {
top: 70px;
}
.entity-search-element {
position: relative;
display: block;
}
.entity-search-element .entity-reference {
position: absolute !important;
top: 7px;
left: 8px;
right: 36px;
z-index: 0;
pointer-events: none;
}
.entity-search-element .entity-reference .entity-reference-element {
pointer-events: none;
}
.entity-search-element .entity-reference-element i.fa-exclamation-triangle {
pointer-events: all;
}
.entity-search-element .entity-reference .entity-name {
display: none;
}
.entity-search-element input {
vertical-align: middle;
width: 100%;
}
.entity-search-element.persistent input {
padding-left: 28px;
padding-right: 28px;
}
.entity-search-element .twitter-typeahead {
vertical-align: middle;
display: block !important;
margin-right: 36px;
}
.entity-search-element .dropdown {
vertical-align: middle;
position: absolute;
top: 0px;
right: 0px;
}
.dropdown-menu i.fa {
margin-right: 6px;
position: relative;

View file

@ -0,0 +1,4 @@
<span class="anchor-element">
<a ng-href="{{ href }}" ng-show="href && !isOnlyText"><span ng-transclude></span></a>
<span ng-show="!href || isOnlyText"><span ng-transclude></span></span>
</span>

View file

@ -1,6 +1,6 @@
<div class="application-info-element" style="padding-bottom: 18px">
<div class="auth-header">
<span class="avatar" size="48" hash="application.avatar"></span>
<span class="avatar" size="48" data="application.avatar"></span>
<h2><a href="{{ application.url }}" target="_blank">{{ application.name }}</a></h2>
<h4>
{{ application.organization.name }}

View file

@ -1 +1,12 @@
<img class="avatar-element" ng-src="{{ AvatarService.getAvatar(_hash, size) }}">
<span class="avatar-element"
ng-style="{'width': size, 'height': size, 'backgroundColor': data.color, 'fontSize': fontSize, 'lineHeight': lineHeight}"
ng-class="data.kind">
<img ng-src="//www.gravatar.com/avatar/{{ data.hash }}?d=404&size={{ size }}"
ng-if="loadGravatar"
ng-show="hasGravatar"
ng-image-watch="imageCallback">
<span class="default-avatar" ng-if="!isLoading && !hasGravatar">
<span class="letter" ng-if="data.kind != 'team' || data.name != 'owners'">{{ data.name.charAt(0).toUpperCase() }}</span>
<span class="letter" ng-if="data.kind == 'team' && data.name == 'owners'">&Omega;</span>
</span>
</span>

View file

@ -15,4 +15,4 @@
<div class="triggered-build-description" build="build" ng-if="build.trigger"></div>
<div ng-if="!build.trigger">Manually Started Build</div>
</div>
</div>

View file

@ -36,4 +36,4 @@
</div>
</div>
</div>
</div>
</div>

View file

@ -19,4 +19,4 @@
<div class="build-description triggered-build-description" build="build"></div>
</div>
</span>
</span>

View file

@ -1,3 +1,3 @@
<span class="co-checkable-item" ng-click="toggleItem()"
ng-class="controller.isChecked(item, controller.checked) ? 'checked': 'not-checked'">
</span>
</span>

View file

@ -1 +1 @@
<li><a href="javascript:void(0)" ng-click="selected()"><span ng-transclude/></a></li>
<li><a href="javascript:void(0)" ng-click="selected()"><span ng-transclude/></a></li>

View file

@ -9,4 +9,4 @@
</span>
<ul class="dropdown-menu" ng-transclude></ul>
</span>
</span>
</span>

View file

@ -22,4 +22,4 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
</div>

View file

@ -1,3 +1,3 @@
<div class="co-floating-bottom-bar">
<span ng-transclude/>
</div>
</div>

View file

@ -2,4 +2,4 @@
<div class="co-m-loader-dot__one"></div>
<div class="co-m-loader-dot__two"></div>
<div class="co-m-loader-dot__three"></div>
</div>
</div>

View file

@ -2,4 +2,4 @@
<div class="co-m-loader-dot__one"></div>
<div class="co-m-loader-dot__two"></div>
<div class="co-m-loader-dot__three"></div>
</div>
</div>

View file

@ -8,4 +8,4 @@
<div class="co-log-viewer-new-logs" ng-show="hasNewLogs" ng-click="moveToBottom()">
New Logs <i class="fa fa-lg fa-arrow-circle-down"></i>
</div>
</div>
</div>

View file

@ -1,3 +1,3 @@
<li>
<a href="javascript:void(0)" ng-click="optionClick()" ng-transclude></a>
</li>
</li>

View file

@ -3,4 +3,4 @@
<i class="fa fa-gear fa-lg dropdown-toggle" data-toggle="dropdown" data-title="Options" bs-tooltip></i>
<ul class="dropdown-menu pull-right" ng-transclude></ul>
</div>
</span>
</span>

View file

@ -1,3 +1,3 @@
<div class="co-step-bar">
<span class="transclude" ng-transclude/>
</div>
</div>

View file

@ -3,4 +3,4 @@
<span class="text" ng-if="text">{{ text }}</span>
<i class="fa fa-lg" ng-if="icon" ng-class="'fa-' + icon"></i>
</span>
</span>
</span>

View file

@ -1 +1 @@
<div class="co-tab-content tab-content col-md-11" ng-transclude></div>
<div class="co-tab-content tab-content col-md-11" ng-transclude></div>

View file

@ -1,3 +1,3 @@
<div class="co-main-content-panel co-tab-panel co-fx-box-shadow-heavy">
<div class="co-tab-container" ng-transclude></div>
</div>
</div>

View file

@ -10,4 +10,4 @@
<span ng-transclude/></span>
</span>
</a>
</li>
</li>

View file

@ -1 +1 @@
<ul class="co-tabs col-md-1" ng-transclude></ul>
<ul class="co-tabs col-md-1" ng-transclude></ul>

View file

@ -1,3 +1,3 @@
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-1">
<span class="co-nav-title-action co-fx-text-shadow" ng-transclude></span>
</div>
</div>

View file

@ -1,3 +1,3 @@
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
<h2 class="co-nav-title-content co-fx-text-shadow" ng-transclude></h2>
</div>
</div>

View file

@ -1 +1 @@
<div class="col-lg-3 col-md-3 hidden-sm hidden-xs" ng-transclude></div>
<div class="col-lg-3 col-md-3 hidden-sm hidden-xs" ng-transclude></div>

View file

@ -1,38 +1,3 @@
<span class="entity-reference-element">
<span ng-if="entity.kind == 'team'">
<i class="fa fa-group" data-title="Team" bs-tooltip="tooltip.title" data-container="body"></i>
<span class="entity-name">
<span ng-if="!getIsAdmin(namespace)">{{entity.name}}</span>
<span ng-if="getIsAdmin(namespace)"><a href="/organization/{{ namespace }}/teams/{{ entity.name }}">{{entity.name}}</a></span>
</span>
</span>
<span ng-if="entity.kind == 'org'">
<span class="avatar" size="avatarSize || 16" hash="entity.avatar"></span>
<span class="entity-name">
<span ng-if="!getIsAdmin(entity.name)">{{entity.name}}</span>
<span ng-if="getIsAdmin(entity.name)"><a href="/organization/{{ entity.name }}">{{entity.name}}</a></span>
</span>
</span>
<span ng-if="entity.kind != 'team' && entity.kind != 'org'">
<span class="avatar" size="avatarSize || 16" hash="entity.avatar" ng-if="showAvatar == 'true' && entity.avatar"></span>
<span ng-if="showAvatar != 'true' || !entity.avatar">
<i class="fa fa-user" ng-show="!entity.is_robot" data-title="User" bs-tooltip="tooltip.title" data-container="body"></i>
<i class="fa fa-wrench" ng-show="entity.is_robot" data-title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
</span>
<span class="entity-name" ng-if="entity.is_robot">
<a href="{{ getRobotUrl(entity.name) }}" ng-if="getIsAdmin(getPrefix(entity.name))">
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
</a>
<span ng-if="!getIsAdmin(getPrefix(entity.name))">
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
</span>
</span>
<span class="entity-name" ng-if="!entity.is_robot">
<span>{{getShortenedName(entity.name)}}</span>
</span>
</span>
<i class="fa fa-exclamation-triangle" ng-if="entity.is_org_member === false"
data-title="This user is not a member of the organization" bs-tooltip="tooltip.title" data-container="body">
</i>
<span quay-include="{'Config.isNewLayout()': 'directives/new-entity-reference.html', '!Config.isNewLayout()': 'directives/old-entity-reference.html'}"></span>
</span>

View file

@ -6,12 +6,26 @@
<span class="caret"></span>
</button>
<ul class="dropdown-menu" ng-class="pullRight == 'true' ? 'pull-right': ''" role="menu" aria-labelledby="entityDropdownMenu">
<li ng-show="lazyLoading" style="padding: 10px"><div class="quay-spinner"></div></li>
<li ng-show="lazyLoading" style="padding: 10px"><div class="cor-loader"></div></li>
<li role="presentation" class="dropdown-header" ng-show="!lazyLoading && !robots && !isAdmin && !teams">
You do not have permission to manage teams and robots for this organization
</li>
<li role="presentation" ng-show="includeTeams && isOrganization && !lazyLoading && isAdmin">
<a role="menuitem" class="new-action" tabindex="-1" href="javascript:void(0)" ng-click="createTeam()">
<i class="fa fa-group"></i> Create team
</a>
</li>
<li role="presentation" ng-show="includeRobots && !lazyLoading && isAdmin">
<a role="menuitem" class="new-action" tabindex="-1" href="javascript:void(0)" ng-click="createRobot()">
<i class="fa fa-wrench"></i>
Create robot account
</a>
</li>
<li role="presentation" class="divider" ng-show="!lazyLoading && robots && isAdmin"></li>
<li role="presentation" class="dropdown-header"
ng-show="!lazyLoading && !teams.length && !robots.length && !((includeTeams && isOrganization && isAdmin) || (includeRobots && isAdmin))">
<span ng-if="includeRobots && includeTeams && isOrganization">
@ -35,34 +49,29 @@
</span>
</li>
<li role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
ng-click="setEntity(team.name, 'team', false)">
<li role="presentation" class="dropdown-header" ng-show="!lazyLoading && teams">Teams</li>
<li class="menuitem" role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
ng-click="setEntity(team.name, 'team', false, team.avatar)">
<a role="menuitem" tabindex="-1" href="javascript:void(0)">
<i class="fa fa-group"></i> <span>{{ team.name }}</span>
<span ng-if="!Config.isNewLayout()">
<i class="fa fa-group"></i> <span>{{ team.name }}</span>
</span>
<span ng-if="Config.isNewLayout()">
<span class="avatar" data="team.avatar" size="16"></span> <span>{{ team.name }}</span>
</span>
</a>
</li>
<li role="presentation" class="divider" ng-show="!lazyLoading && teams && (isAdmin || robots)"></li>
<li role="presentation" ng-repeat="robot in robots" ng-show="!lazyLoading">
<li role="presentation" class="dropdown-header" ng-show="!lazyLoading && robots">Robot Accounts</li>
<li class="menuitem" role="presentation" ng-repeat="robot in robots" ng-show="!lazyLoading">
<a role="menuitem" tabindex="-1" href="javascript:void(0)" ng-click="setEntity(robot.name, 'user', true)">
<i class="fa fa-wrench"></i> <span>{{ robot.name }}</span>
</a>
</li>
<li role="presentation" class="divider" ng-show="!lazyLoading && robots && isAdmin"></li>
<li role="presentation" ng-show="includeTeams && isOrganization && !lazyLoading && isAdmin">
<a role="menuitem" class="new-action" tabindex="-1" href="javascript:void(0)" ng-click="createTeam()">
<i class="fa fa-group"></i> Create team
</a>
</li>
<li role="presentation" ng-show="includeRobots && !lazyLoading && isAdmin">
<a role="menuitem" class="new-action" tabindex="-1" href="javascript:void(0)" ng-click="createRobot()">
<i class="fa fa-wrench"></i>
Create robot account
</a>
</li>
</ul>
</div>
</span>

View file

@ -67,4 +67,4 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
</div>

View file

@ -1,3 +1,3 @@
<span class="filter-control-element" ng-class="filter == value ? 'selected': 'not-selected'">
<a href="javascript:void(0)" ng-click="setFilter()"><span ng-transclude/></a>
</span>
</span>

View file

@ -23,7 +23,7 @@
<ul class="nav navbar-nav navbar-right visible-xs" ng-switch on="user.anonymous">
<li ng-switch-when="false">
<a href="/user/" class="user-view" target="{{ appLinkTarget() }}">
<span class="avatar" size="32" hash="user.avatar"></span>
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
</a>
</li>
@ -48,7 +48,7 @@
<li class="dropdown" ng-switch-when="false">
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<span class="avatar" size="32" hash="user.avatar"></span>
<span class="avatar" size="32" data="user.avatar"></span>
{{ user.username }}
<span class="notifications-bubble"></span>
<b class="caret"></b>

View file

@ -30,4 +30,4 @@
</span>
</div>
</div>
</div>
</div>

View file

@ -102,4 +102,4 @@
has-changes="hasImageChanges"></div>
</span>
</div>
</div>
</div>

View file

@ -12,4 +12,4 @@
</div>
<div class="image-layer-dot"></div>
<div class="image-layer-line"></div>
</div>
</div>

View file

@ -1,19 +1,19 @@
<span class="namespace-selector-dropdown">
<span ng-show="user.organizations.length == 0">
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="avatar" size="24" data="user.avatar"></span>
<span class="namespace-name">{{user.username}}</span>
</span>
<div class="btn-group" ng-show="user.organizations.length > 0">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="avatar" size="16" hash="namespaceObj.avatar"></span>
<span class="avatar" size="16" data="namespaceObj.avatar"></span>
{{namespace}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li class="namespace-item" ng-repeat="org in user.organizations"
ng-class="(requireCreate && !namespaces[org.name].can_create_repo) ? 'disabled' : ''">
<a class="namespace" href="javascript:void(0)" ng-click="setNamespace(org)">
<span class="avatar" size="24" hash="org.avatar"></span>
<span class="avatar" size="24" data="org.avatar"></span>
<span class="namespace-name">{{ org.name }}</span>
</a>
@ -25,7 +25,7 @@
<li class="divider"></li>
<li>
<a class="namespace" href="javascript:void(0)" ng-click="setNamespace(user)">
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="avatar" size="24" data="user.avatar"></span>
<span class="namespace-name">{{ user.username }}</span>
</a>
</li>

View file

@ -0,0 +1,40 @@
<span class="new-entity-reference" data-title="{{ getTitle(entity) }} {{ entity.name }}" bs-tooltip>
<span ng-switch on="entity.kind">
<!-- Team -->
<span ng-switch-when="team">
<span class="avatar" data="entity.avatar" size="avatarSize || 16"></span>
<span class="entity-name anchor"
href="/organization/{{ namespace }}/teams/{{ entity.name }}"
is-only-text="!getIsAdmin(namespace)">
{{ entity.name }}
</span>
</span>
<!-- Organization -->
<span ng-switch-when="org">
<span class="avatar" size="avatarSize || 16" data="entity.avatar"></span>
<span class="entity-name anchor" href="/organization/{{ entity.name }}"
is-only-text="!getIsAdmin(entity.name)">
</span>
</span>
<!-- User or Robot -->
<span ng-switch-when="user">
<!-- User -->
<span ng-if="!entity.is_robot">
<span class="avatar" size="avatarSize || 16" data="entity.avatar"></span>
<span class="entity-name">{{ entity.name }}</span>
</span>
<!-- Robot -->
<span ng-if="entity.is_robot">
<i class="fa fa-lg fa-wrench"></i>
<span class="entity-name anchor" href="{{ getRobotUrl(entity.name) }}"
is-only-text="!getIsAdmin(getPrefix(entity.name))">
<span class="prefix">{{ getPrefix(entity.name) }}+</span>
<span>{{ getShortenedName(entity.name) }}</span>
</span>
</span>
</span>
</span>
</span>

View file

@ -3,7 +3,7 @@
<div class="circle" ng-class="getClass(notification)"></div>
<div class="message" ng-bind-html="getMessage(notification)"></div>
<div class="orginfo" ng-if="notification.organization">
<span class="avatar" size="24" hash="getAvatar(notification.organization)"></span>
<span class="avatar" size="24" data="getAvatar(notification.organization)"></span>
<span class="orgname">{{ notification.organization }}</span>
</div>
</div>

View file

@ -0,0 +1,39 @@
<!-- DEPRECATED! -->
<span class="old-entity-reference">
<span ng-if="entity.kind == 'team'">
<i class="fa fa-group" data-title="Team" bs-tooltip="tooltip.title" data-container="body"></i>
<span class="entity-name">
<span ng-if="!getIsAdmin(namespace)">{{entity.name}}</span>
<span ng-if="getIsAdmin(namespace)"><a href="/organization/{{ namespace }}/teams/{{ entity.name }}">{{entity.name}}</a></span>
</span>
</span>
<span ng-if="entity.kind == 'org'">
<span class="avatar" size="avatarSize || 16" data="entity.avatar"></span>
<span class="entity-name">
<span ng-if="!getIsAdmin(entity.name)">{{entity.name}}</span>
<span ng-if="getIsAdmin(entity.name)"><a href="/organization/{{ entity.name }}">{{entity.name}}</a></span>
</span>
</span>
<span ng-if="entity.kind != 'team' && entity.kind != 'org'">
<span class="avatar" size="avatarSize || 16" data="entity.avatar" ng-if="showAvatar == 'true' && entity.avatar"></span>
<span ng-if="showAvatar != 'true' || !entity.avatar">
<i class="fa fa-user" ng-show="!entity.is_robot" data-title="User" bs-tooltip="tooltip.title" data-container="body"></i>
<i class="fa fa-wrench" ng-show="entity.is_robot" data-title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
</span>
<span class="entity-name" ng-if="entity.is_robot">
<a href="{{ getRobotUrl(entity.name) }}" ng-if="getIsAdmin(getPrefix(entity.name))">
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
</a>
<span ng-if="!getIsAdmin(getPrefix(entity.name))">
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
</span>
</span>
<span class="entity-name" ng-if="!entity.is_robot">
<span>{{getShortenedName(entity.name)}}</span>
</span>
</span>
<i class="fa fa-exclamation-triangle" ng-if="entity.is_org_member === false"
data-title="This user is not a member of the organization" bs-tooltip="tooltip.title" data-container="body">
</i>
</span>

View file

@ -1,5 +1,5 @@
<div class="organization-header-element">
<span class="avatar" size="24" hash="organization.avatar"></span>
<span class="avatar" size="24" data="organization.avatar"></span>
<span class="organization-name" ng-show="teamName || clickable">
<a href="/organization/{{ organization.name }}">{{ organization.name }}</a>
</span>

View file

@ -72,4 +72,4 @@
<h4>Network Usage (Bytes)</h4>
<div class="realtime-line-chart" data="data.count.network" labels="['Bytes In', 'Bytes Out']" counter="counter"></div>
</div>
</div>
</div>

View file

@ -10,4 +10,4 @@
<i class="fa fa-calendar" style="margin-right: 6px;"></i>
<a ng-href="{{ scheduled.shortlink }}" class="quay-service-status-description">{{ scheduled.name }}</a>
</div>
</div>
</div>

View file

@ -3,4 +3,4 @@
ng-if="indicator != 'loading'"></span>
<span class="cor-loader-inline" ng-if="indicator == 'loading'"></span>
<a href="http://status.quay.io" class="quay-service-status-description">{{ description }}</a>
</span>
</span>

View file

@ -3,4 +3,4 @@
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>
</div>

View file

@ -3,4 +3,4 @@
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>
</div>

View file

@ -8,7 +8,7 @@
Starred
</div>
<div ng-if="!starred" class="repo-list-title">
<span class="avatar" size="24" hash="namespace.avatar"></span>
<span class="avatar" size="24" data="namespace.avatar"></span>
<span ng-if="!isOrganization(namespace.name)">{{ namespace.name }}</span>
<a ng-if="isOrganization(namespace.name)"
href="/organization/{{ namespace.name }}">{{ namespace.name }}</a>

View file

@ -2,4 +2,4 @@
<i ng-class="repository.is_starred ? 'starred fa fa-star' : 'fa fa-star-o'"
class="star-icon" ng-click="toggleStar()">
</i>
</span>
</span>

View file

@ -72,4 +72,4 @@
repository="repository"
counter="showNewNotificationCounter"
notification-created="handleNotificationCreated(notification)"></div>
</div>
</div>

View file

@ -15,7 +15,8 @@
<tr ng-repeat="(name, permission) in permissionResources.team.value">
<td class="team entity">
<span class="entity-reference" namespace="repository.namespace"
entity="buildEntityForPermission(name, permission, 'team')">
entity="buildEntityForPermission(name, permission, 'team')"
avatar-size="24">
</span>
</td>
<td class="user-permissions">
@ -35,7 +36,8 @@
<tr ng-repeat="(name, permission) in permissionResources.user.value">
<td class="{{ 'user entity ' + (permission.is_org_member ? '' : 'outside') }}">
<span class="entity-reference" namespace="repository.namespace"
entity="buildEntityForPermission(name, permission, 'user')">
entity="buildEntityForPermission(name, permission, 'user')"
avatar-size="24">
</span>
</td>
<td class="user-permissions">
@ -79,4 +81,4 @@
dialog-action-title="Grant Permission">
The selected user is outside of your organization. Are you sure you want to grant the user access to this repository?
</div>
</div>
</div>

View file

@ -32,4 +32,4 @@
</tbody>
</table>
</div>
</div>
</div>

View file

@ -1,4 +1,4 @@
<span class="source-commit-link-elememt">
<i class="fa fa-dot-circle-o" data-title="Commit" data-container="body" bs-tooltip></i>
<a ng-href="{{ getUrl(commitSha, urlTemplate) }}" target="_blank">{{ commitSha.substring(0, 8) }}</a>
</span>
</span>

View file

@ -12,4 +12,4 @@
<a ng-href="{{ getUrl(ref, tagTemplate, 'tag') }}" target="_blank">{{ getTitle(ref) }}</a>
</span>
</span>
</span>
</span>

View file

@ -84,4 +84,4 @@
The following images and any other images not referenced by a tag will be deleted:
</div>
</div>
</div>
</div>

View file

@ -1,31 +1,62 @@
<div class="teams-manager-element">
<div class="manager-header">
<span class="popup-input-button" pattern="TEAM_PATTERN" placeholder="'Team Name'"
submitted="createTeam(value)">
submitted="createTeam(value)" ng-show="organization.is_admin">
<i class="fa fa-plus" style="margin-right: 6px;"></i> Create New Team
</span>
<h3>Teams</h3>
</div>
<div class="team-listing" ng-repeat="(name, team) in organization.teams">
<div id="team-{{name}}" class="row">
<div class="col-sm-7 col-md-8">
<div class="team-title">
<i class="fa fa-group"></i>
<span ng-show="team.can_view">
<a href="/organization/{{ organization.name }}/teams/{{ team.name }}">{{ team.name }}</a>
</span>
<span ng-show="!team.can_view">
{{ team.name }}
</span>
</div>
<div class="row hidden-xs">
<div class="col-md-4 col-md-offset-8 col-sm-5 col-sm-offset-7 header-col" ng-show="organization.is_admin">
<span class="header-text">Team Permissions</span>
<i class="info-icon fa fa-info-circle" data-placement="bottom" data-original-title="" data-title=""
data-content="Global permissions for the team and its members<br><br><dl><dt>Member</dt><dd>Permissions are assigned on a per repository basis</dd><dt>Creator</dt><dd>A team can create its own repositories</dd><dt>Admin</dt><dd>A team has full control of the organization</dd></dl>"
data-html="true"
data-trigger="hover"
bs-popover></i>
</div>
</div>
<div class="team-description markdown-view" content="team.description" first-line-only="true"></div>
<div class="team-listing" ng-repeat="team in orderedTeams">
<div id="team-{{team.name}}" class="row">
<div class="col-sm-7 col-md-8">
<div class="team-title">
<span class="avatar" data="team.avatar" size="30"></span>
<span ng-show="team.can_view">
<a href="/organization/{{ organization.name }}/teams/{{ team.name }}">{{ team.name }}</a>
</span>
<span ng-show="!team.can_view">
{{ team.name }}
</span>
</div>
<div class="col-sm-5 col-md-4 control-col" ng-show="organization.is_admin">
<span class="role-group" current-role="team.role" role-changed="setRole(role, team.name)" roles="teamRoles"></span>
<button class="btn btn-sm btn-danger" ng-click="askDeleteTeam(team.name)">Delete</button>
<div class="team-description markdown-view" content="team.description" first-line-only="true"></div>
<div class="team-member-list" ng-if="members[team.name]">
<div class="cor-loader" ng-if="!members[team.name].members"></div>
<span class="team-member" ng-repeat="member in members[team.name].members | limitTo: 25">
<span data-title="{{ member.name }}" bs-tooltip>
<span class="avatar" data="member.avatar" size="26" ng-if="!member.is_robot"></span>
<i class="fa fa-wrench fa-lg" ng-if="member.is_robot"></i>
</span>
</span>
<span class="team-member-more"
ng-if="members[team.name].members.length > 25">+ {{ members[team.name].members.length - 25 }} more team members.</span>
<span class="team-member-more"
ng-if="!members[team.name].members.length">(Empty Team)</span>
</div>
</div>
<div class="col-sm-5 col-md-4 control-col" ng-show="organization.is_admin">
<span class="role-group" current-role="team.role" role-changed="setRole(role, team.name)" roles="teamRoles"></span>
<span class="cor-options-menu">
<span class="cor-option" option-click="askDeleteTeam(team.name)">
<i class="fa fa-times"></i> Delete Team {{ team.name }}
</span>
</span>
</div>
</div>
</div>
</div>
</div>

View file

@ -62,4 +62,4 @@
<!-- Unknown -->
<span ng-switch-default></span>
</span>
</div>
</div>

View file

@ -0,0 +1,17 @@
/**
* Adds a ng-image-watch attribute, which is a callback invoked when the image is loaded or fails.
*/
angular.module('quay').directive('ngImageWatch', function () {
return {
restrict: 'A',
link: function postLink($scope, $element, $attr) {
$element.bind('error', function() {
$scope.$eval($attr.ngImageWatch)(false);
});
$element.bind('load', function() {
$scope.$eval($attr.ngImageWatch)(true);
});
}
};
});

View file

@ -120,6 +120,8 @@ angular.module('quay').directive('quayClasses', function(Features, Config) {
/**
* Adds a quay-include attribtue that adds a template solely if the expression evaluates to true.
* Automatically adds the Features and Config services to the scope.
*
Usage: quay-include="{'Features.BILLING': 'partials/landing-normal.html', '!Features.BILLING': 'partials/landing-login.html'}"
*/
angular.module('quay').directive('quayInclude', function($compile, $templateCache, $http, Features, Config) {
return {
@ -127,7 +129,7 @@ angular.module('quay').directive('quayInclude', function($compile, $templateCach
restrict: 'A',
link: function($scope, $element, $attr, ctrl) {
var getTemplate = function(templateName) {
var templateUrl = '/static/partials/' + templateName;
var templateUrl = '/static/' + templateName;
return $http.get(templateUrl, {cache: $templateCache});
};

View file

@ -0,0 +1,19 @@
/**
* An element which displays its contents wrapped in an <a> tag, but only if the href is not null.
*/
angular.module('quay').directive('anchor', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/anchor.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'href': '@href',
'isOnlyText': '=isOnlyText'
},
controller: function($scope, $element) {
}
};
return directiveDefinitionObject;
});

View file

@ -1,5 +1,5 @@
/**
* An element which displays an avatar for the given {email,name} or hash.
* An element which displays an avatar for the given avatar data.
*/
angular.module('quay').directive('avatar', function () {
var directiveDefinitionObject = {
@ -9,25 +9,36 @@ angular.module('quay').directive('avatar', function () {
transclude: true,
restrict: 'C',
scope: {
'hash': '=hash',
'email': '=email',
'name': '=name',
'data': '=data',
'size': '=size'
},
controller: function($scope, $element, AvatarService) {
controller: function($scope, $element, AvatarService, Config, UIService) {
$scope.AvatarService = AvatarService;
$scope.Config = Config;
$scope.isLoading = true;
$scope.hasGravatar = false;
$scope.loadGravatar = false;
var refreshHash = function() {
if (!$scope.name && !$scope.email) { return; }
$scope._hash = AvatarService.computeHash($scope.email, $scope.name);
$scope.imageCallback = function(r) {
$scope.isLoading = false;
$scope.hasGravatar = r;
};
$scope.$watch('hash', function(hash) {
$scope._hash = hash;
$scope.$watch('size', function(size) {
size = size * 1 || 16;
$scope.fontSize = (size - 4) + 'px';
$scope.lineHeight = size + 'px';
});
$scope.$watch('name', refreshHash);
$scope.$watch('email', refreshHash);
$scope.$watch('data', function(data) {
if (!data) { return; }
$scope.loadGravatar = Config.AVATAR_KIND == 'gravatar' &&
(data.kind == 'user' || data.kind == 'org');
$scope.isLoading = $scope.loadGravatar;
$scope.hasGravatar = false;
});
}
};
return directiveDefinitionObject;

View file

@ -39,6 +39,21 @@ angular.module('quay').directive('entityReference', function () {
return '/organization/' + org['name'] + '/admin?tab=robots&showRobot=' + UtilService.textToSafeHtml(name);
};
$scope.getTitle = function(entity) {
if (!entity) { return ''; }
switch (entity.kind) {
case 'org':
return 'Organization';
case 'team':
return 'Team';
case 'user':
return entity.is_robot ? 'Robot Account' : 'User';
}
};
$scope.getPrefix = function(name) {
if (!name) { return ''; }
var plus = name.indexOf('+');

View file

@ -56,6 +56,8 @@ angular.module('quay').directive('entitySearch', function () {
$scope.currentEntityInternal = $scope.currentEntity;
$scope.Config = Config;
var isSupported = function(kind, opt_array) {
return $.inArray(kind, opt_array || $scope.allowedEntities || ['user', 'team', 'robot']) >= 0;
};
@ -102,7 +104,7 @@ angular.module('quay').directive('entitySearch', function () {
}
CreateService.createOrganizationTeam(ApiService, $scope.namespace, teamname, function(created) {
$scope.setEntity(created.name, 'team', false);
$scope.setEntity(created.name, 'team', false, created.avatar);
$scope.teams[teamname] = created;
});
});
@ -121,17 +123,18 @@ angular.module('quay').directive('entitySearch', function () {
}
CreateService.createRobotAccount(ApiService, $scope.isOrganization, $scope.namespace, robotname, function(created) {
$scope.setEntity(created.name, 'user', true);
$scope.setEntity(created.name, 'user', true, created.avatar);
$scope.robots.push(created);
});
});
};
$scope.setEntity = function(name, kind, is_robot) {
$scope.setEntity = function(name, kind, is_robot, avatar) {
var entity = {
'name': name,
'kind': kind,
'is_robot': is_robot
'is_robot': is_robot,
'avatar': avatar
};
if ($scope.isOrganization) {

View file

@ -68,7 +68,8 @@ angular.module('quay').directive('repositoryPermissionsTable', function () {
'kind': kind,
'name': name,
'is_robot': permission.is_robot,
'is_org_member': permission.is_org_member
'is_org_member': permission.is_org_member,
'avatar': permission.avatar
};
};

View file

@ -19,6 +19,45 @@ angular.module('quay').directive('teamsManager', function () {
{ 'id': 'admin', 'title': 'Admin', 'kind': 'primary' }
];
$scope.members = {};
$scope.orderedTeams = [];
var loadTeamMembers = function() {
if (!$scope.organization) { return; }
for (var name in $scope.organization.teams) {
if (!$scope.organization.teams.hasOwnProperty(name)) { continue; }
loadMembersOfTeam(name);
}
};
var loadMembersOfTeam = function(name) {
var params = {
'orgname': $scope.organization.name,
'teamname': name
};
$scope.members[name] = {};
ApiService.getOrganizationTeamMembers(null, params).then(function(resp) {
$scope.members[name].members = resp.members;
}, function() {
delete $scope.members[name];
});
};
var loadOrderedTeams = function() {
if (!$scope.organization) { return; }
$scope.orderedTeams = [];
$scope.organization.ordered_teams.map(function(name) {
$scope.orderedTeams.push($scope.organization.teams[name]);
});
};
$scope.$watch('organization', loadOrderedTeams);
$scope.$watch('organization', loadTeamMembers);
$scope.setRole = function(role, teamname) {
var previousRole = $scope.organization.teams[teamname].role;
$scope.organization.teams[teamname].role = role;
@ -54,6 +93,10 @@ angular.module('quay').directive('teamsManager', function () {
var orgname = $scope.organization.name;
CreateService.createOrganizationTeam(ApiService, orgname, teamname, function(created) {
$scope.organization.teams[teamname] = created;
$scope.members[teamname] = {};
$scope.members[teamname].members = [];
$scope.organization.ordered_teams.push(teamname);
$scope.orderedTeams.push(created);
});
};
@ -72,6 +115,12 @@ angular.module('quay').directive('teamsManager', function () {
};
ApiService.deleteOrganizationTeam(null, params).then(function() {
var index = $scope.organization.ordered_teams.indexOf(teamname);
if (index >= 0) {
$scope.organization.ordered_teams.splice(index, 1);
}
loadOrderedTeams();
delete $scope.organization.teams[teamname];
}, ApiService.errorDisplay('Cannot delete team'));
};

View file

@ -14,7 +14,9 @@ angular.module('quay').factory('AvatarService', ['Config', '$sanitize', 'md5',
break;
case 'gravatar':
return '//www.gravatar.com/avatar/' + hash + '?d=identicon&size=' + size;
// TODO(jschorr): Remove once the new layout is in place everywhere.
var default_kind = Config.isNewLayout() ? '404' : 'identicon';
return '//www.gravatar.com/avatar/' + hash + '?d=' + default_kind + '&size=' + size;
break;
}
};

View file

@ -71,5 +71,10 @@ angular.module('quay').factory('Config', [function() {
return value;
};
config.isNewLayout = function() {
// TODO(jschorr): Remove once new layout is in place for everyone.
return document.cookie.toString().indexOf('quay.exp-new-layout=true') >= 0;
};
return config;
}]);

View file

@ -61,4 +61,4 @@
</div>
</div>
</div>
</div>
</div>

View file

@ -56,4 +56,4 @@
</div>
</div>
</div>
</div>
</div>

View file

@ -47,7 +47,7 @@
<div class="signup-form"></div>
</div>
<div ng-show="!user.anonymous" class="user-welcome">
<span class="avatar" size="128" hash="user.avatar"></span>
<span class="avatar" size="128" data="user.avatar"></span>
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
<a class="btn btn-success" href="/new/">Create a new repository</a>

View file

@ -46,7 +46,7 @@
<div class="signup-form"></div>
</div>
<div ng-show="!user.anonymous" class="user-welcome">
<span class="avatar" size="128" hash="user.avatar"></span>
<span class="avatar" size="128" data="user.avatar"></span>
<div class="sub-message">Welcome <b>{{ user.username }}</b>!</div>
<a ng-show="my_repositories.value" class="btn btn-primary" href="/repository/">Browse all repositories</a>
<a class="btn btn-success" href="/new/">Create a new repository</a>

View file

@ -1,3 +1,3 @@
<div quay-include="{'Features.BILLING': 'landing-normal.html', '!Features.BILLING': 'landing-login.html'}" onload="chromify()">
<div quay-include="{'Features.BILLING': 'partials/landing-normal.html', '!Features.BILLING': 'partials/landing-login.html'}" onload="chromify()">
<span class="quay-spinner"></span>
</div>

View file

@ -10,7 +10,7 @@
<span class="avatar" size="48" email="application.avatar_email" name="application.name"></span>
<h2>{{ application.name || '(Untitled)' }}</h2>
<h4>
<span class="avatar" size="24" hash="organization.avatar" style="vertical-align: middle; margin-right: 4px;"></span>
<span class="avatar" size="24" data="organization.avatar" style="vertical-align: middle; margin-right: 4px;"></span>
<span style="vertical-align: middle"><a href="/organization/{{ organization.name }}/admin">{{ organization.name }}</a></span>
</h4>
</div>
@ -100,7 +100,7 @@
<div style="margin-bottom: 20px">
<strong>Note:</strong> The generated token will act on behalf of user
<span class="avatar" hash="user.avatar" size="16" style="margin-left: 6px; margin-right: 4px;"></span>
<span class="avatar" data="user.avatar" size="16" style="margin-left: 6px; margin-right: 4px;"></span>
{{ user.username }}
</div>

View file

@ -5,7 +5,7 @@
<div class="cor-title">
<span class="cor-title-link"></span>
<span class="cor-title-content">
<span class="avatar" size="32" hash="organization.avatar"></span>
<span class="avatar" size="32" data="organization.avatar"></span>
<span class="organization-name">{{ organization.name }}</span>
</span>
</div>
@ -124,4 +124,4 @@
</div> <!-- /cor-tab-content -->
</div>
</div>
</div>
</div>

View file

@ -23,7 +23,7 @@
<h2>Organizations</h2>
<div class="organization-listing" ng-repeat="organization in user.organizations">
<span class="avatar" size="32" hash="organization.avatar"></span>
<span class="avatar" size="32" data="organization.avatar"></span>
<a class="org-title" href="/organization/{{ organization.name }}">{{ organization.name }}</a>
</div>
</div>

View file

@ -34,11 +34,11 @@
<ul class="namespaces-list">
<li ng-repeat="namespace in namespaces">
<span ng-if="!isOrganization(namespace.name)">
<span class="avatar" size="30" hash="namespace.avatar"></span>
<span class="avatar" size="30" data="namespace.avatar"></span>
{{ namespace.name }}
</span>
<a href="/organization/{{ namespace.name }}" ng-if="isOrganization(namespace.name)">
<span class="avatar" size="30" hash="namespace.avatar"></span>
<span class="avatar" size="30" data="namespace.avatar"></span>
{{ namespace.name }}
</a>
</li>

View file

@ -93,4 +93,4 @@
</div> <!-- /cor-tab-content -->
</div>
</div>
</div>
</div>

View file

@ -296,4 +296,4 @@
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div><!-- /.modal -->

View file

@ -139,7 +139,7 @@
<tr ng-repeat="current_user in (users | filter:search | orderBy:'username' | limitTo:100)"
class="user-row">
<td>
<span class="avatar" hash="current_user.avatar" size="24"></span>
<span class="avatar" data="current_user.avatar" size="24"></span>
</td>
<td>
<span class="labels">

View file

@ -70,7 +70,7 @@
<span class="entity-reference" entity="member" namespace="organization.name" show-avatar="true" avatar-size="32"></span>
</span>
<span class="invite-listing" ng-if="member.kind == 'invite'">
<span class="avatar" size="32" hash="member.avatar"></span>
<span class="avatar" size="32" data="member.avatar"></span>
{{ member.email }}
</span>
</td>

View file

@ -9,7 +9,7 @@
<div class="user-admin cor-container" ng-show="!user.anonymous">
<div class="row">
<div class="organization-header-element">
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="avatar" size="24" data="user.avatar"></span>
<span class="organization-name">
{{ user.username }}
</span>
@ -72,7 +72,7 @@
<tr class="auth-info" ng-repeat="authInfo in authorizedApps">
<td>
<span class="avatar" size="16" hash="authInfo.application.avatar"></span>
<span class="avatar" size="16" data="authInfo.application.avatar"></span>
<a href="{{ authInfo.application.url }}" ng-if="authInfo.application.url" target="_blank"
data-title="{{ authInfo.application.description || authInfo.application.name }}" bs-tooltip>
{{ authInfo.application.name }}
@ -291,7 +291,7 @@
<div class="form-group">
<label for="orgName">Organization Name</label>
<div class="existing-data">
<span class="avatar" size="24" hash="user.avatar"></span>
<span class="avatar" size="24" data="user.avatar"></span>
{{ user.username }}</div>
<span class="description">This will continue to be the namespace for your repositories</span>
</div>