UX: Show badges as cards on the badges index and show page

This commit is contained in:
Robin Ward 2016-03-25 15:32:48 -04:00
parent fc9519af52
commit 27c793a990
10 changed files with 193 additions and 95 deletions

View file

@ -0,0 +1,15 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
size: 'medium',
classNameBindings: [':badge-card', 'size'],
@computed('size')
summary(size) {
if (size === 'large') {
return Discourse.Emoji.unescape(this.get('badge.long_description') || '');
}
return this.get('badge.translatedDescription');
}
});

View file

@ -1,11 +1,16 @@
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
tagName: 'i',
classNameBindings: [':fa', 'iconClass'],
tagName: 'span',
classNameBindings: [':check-display', 'status'],
@computed('checked')
iconClass(checked) {
return checked ? 'fa-check' : 'fa-times';
status(checked) {
return checked ? 'status-checked' : 'status-unchecked';
},
render(buffer) {
const icon = this.get('checked') ? 'check' : 'times';
buffer.push(`<i class='fa fa-${icon}'></i>`);
}
});

View file

@ -44,11 +44,6 @@ export default Ember.Controller.extend({
@observes('canLoadMore')
_showFooter() {
this.set("controllers.application.showFooter", !this.get("canLoadMore"));
},
@computed('model.long_description')
longDescription(modelLongDesc) {
return Discourse.Emoji.unescape(modelLongDesc || '');
}
});

View file

@ -33,13 +33,6 @@ const Badge = RestModel.extend({
return I18n.t(i18nKey, {defaultValue: this.get('name')});
}.property('name', 'i18nNameKey'),
/**
The i18n translated description for this badge. Returns the null if no
translation exists.
@property translatedDescription
@type {String}
**/
translatedDescription: function() {
const i18nKey = "badges.badge." + this.get('i18nNameKey') + ".description";
let translation = I18n.t(i18nKey);

View file

@ -1,21 +1,17 @@
<div class='container badges'>
<h1>{{i18n 'badges.title'}}</h1>
<table class='badges-listing'>
<tbody>
<div class='badge-groups'>
{{#each bg in badgeGroups}}
<tr class='title'>
<td colspan=4><h3>{{bg.badgeGrouping.displayName}}</h3></td>
</tr>
{{#each b in bg.badges}}
<tr>
<td class='granted'>{{#if b.has_badge}}<i class='fa fa-check'></i>{{/if}}</td>
<td class='badge'>{{user-badge badge=b}}</td>
<td class='description'>{{{b.displayDescriptionHtml}}}</td>
<td class='grant-count'><span title="{{i18n 'badges.granted' count=b.grant_count}}">{{b.grant_count}}</span></td>
</tr>
{{/each}}
{{/each}}
</tbody>
</table>
<div class='badge-grouping'>
<div class='title'>
<h3>{{bg.badgeGrouping.displayName}}</h3>
</div>
{{#each bg.badges as |b|}}
{{badge-card badge=b}}
{{/each}}
</div>
{{/each}}
</div>
</div>

View file

@ -5,18 +5,16 @@
{{model.displayName}}
</h1>
{{#if longDescription}}
<div class='long-description banner'>
{{{longDescription}}}
<div class='show-badge-details'>
{{badge-card badge=model size="large"}}
<div class='badge-grant-info'>
<div>
<div class='grant-info-item'>
{{check-mark checked=model.allow_title}} {{i18n 'badges.allow_title'}}
</div>
<div class='grant-info-item'>
{{check-mark checked=model.multiple_grant}} {{i18n 'badges.multiple_grant'}}
</div>
{{/if}}
<div class='badges-listing'>
<div class='row'>
<div class='grant-count'>{{i18n 'badges.granted' count=grantCount}}</div>
<div class='info'>
{{i18n 'badges.allow_title'}} {{check-mark checked=model.allow_title}}<br>
{{i18n 'badges.multiple_grant'}} {{check-mark checked=model.multiple_grant}}
</div>
</div>
</div>

View file

@ -0,0 +1,19 @@
{{#link-to 'badges.show' badge}}
{{#if badge.grant_count}}
<span class='grant-count' title={{i18n 'badges.granted' count=badge.grant_count}}>{{badge.grant_count}}</span>
{{/if}}
{{#if badge.has_badge}}
<span class='check-display status-checked'>{{fa-icon "check"}}</span>
{{/if}}
<div class='badge-contents'>
<div class='badge-icon {{badge.badgeTypeClassName}}'>
{{icon-or-image badge.icon}}
</div>
<div class='badge-info'>
<div class='badge-info-item'>
<h3>{{badge.displayName}}</h3>
<div class='badge-summary'>{{{summary}}}</div>
</div>
</div>
</div>
{{/link-to}}

View file

@ -32,6 +32,7 @@
}
}
/* User badge listing. */
.user-badges-list {
text-align: center;
@ -64,55 +65,6 @@
}
}
/* Badge listing in /badges. */
.badges-listing {
margin: 20px 0;
tr.title {
border-bottom: 2px solid dark-light-diff($primary, $secondary, 90%, -60%);
}
td {
padding: 10px 0;
}
width: 90%;
padding: 10px;
display: table;
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
.row {
display: table-row;
> div {
display: table-cell;
vertical-align: middle;
}
}
.user-badge {
font-size: $base-font-size;
}
.grant-count {
font-size: 120%;
font-weight: bold;
}
.badge, .grant-count {
white-space: nowrap;
}
.info {
font-size: 0.9em;
text-align: right;
}
.description {
}
}
@media all and (max-width: 750px) {
.show-badge .user-badge-with-posts .badge-user a.post-link {
width: auto;
@ -202,3 +154,127 @@
margin-bottom: 15px;
margin-top: 15px;
}
.badge-card {
position: relative;
display: inline-block;
background-color: dark-light-diff($primary, $secondary, 95%, -65%);
margin-right: 5px;
margin-bottom: 10px;
box-shadow: 1px 1px 3px rgba(0.0, 0.0, 0.0, 0.2);
.check-display {
position: absolute;
left: 5px;
top: 5px;
}
.grant-count {
position: absolute;
right: 5px;
top: 5px;
font-weight: bold;
color: dark-light-diff($primary, $secondary, 50%, -65%);
font-size: 1.2em;
}
.badge-contents {
display: flex;
flex-direction: row;
min-height: 128px;
.badge-icon {
min-width: 90px;
display: flex;
align-items: center;
justify-content: center;
background-color: dark-light-diff($primary, $secondary, 92%, -60%);
font-size: 3em;
&.badge-type-gold .fa {
color: #ffd700 !important;
}
&.badge-type-silver .fa {
color: #c0c0c0 !important;
}
&.badge-type-bronze .fa {
color: #cd7f32 !important;
}
}
.badge-info {
display: flex;
align-items: center;
justify-content: center;
padding: 15px;
color: $primary;
h3 {
margin-bottom: 0.25em;
}
}
}
}
.badge-card.medium {
width: 360px;
}
.badge-card.large {
width: 750px;
a {
cursor: default;
}
}
.badge-groups {
margin: 20px 0;
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
h3 {
margin-bottom: 1.0em;
}
}
.badge-grouping {
margin-bottom: 1.5em;
}
.show-badge-details {
display: flex;
flex-direction: row;
margin-bottom: 2em;
margin-top: 1em;
.badge-grant-info {
display: flex;
align-items: center;
margin-left: 1em;
}
.grant-info-item {
margin-bottom: 1em;
color: dark-light-choose(scale-color($primary, $lightness: 40%), scale-color($secondary, $lightness: 60%));
}
}
.check-display {
display: inline-block;
width: 18px;
border-radius: 10px;
text-align: center;
.fa {
font-size: 0.9em;
color: $secondary;
}
}
.check-display.status-checked {
background-color: dark-light-diff($primary, $secondary, 40%, -60%);
}
.check-display.status-unchecked {
background-color: $danger;
}

View file

@ -9,4 +9,5 @@ class BadgeIndexSerializer < BadgeSerializer
def has_badge
@options[:user_badges].include?(object.id)
end
end

View file

@ -5,12 +5,12 @@ acceptance("Badges");
test("Visit Badge Pages", () => {
visit("/badges");
andThen(() => {
ok(exists('.badges-listing tr'), "has a list of badges");
ok(exists('.badge-groups .badge-card'), "has a list of badges");
});
visit("/badges/9/autobiographer");
andThen(() => {
ok(exists('.badges-listing div'), "has the badge in the listing");
ok(exists('.badge-card'), "has the badge in the listing");
ok(exists('.user-info'), "has the list of users with that badge");
});
});