FEATURE: badge grouping UI

FIX: not loading more badges on badge show page
This commit is contained in:
Sam 2014-07-18 15:46:36 +10:00
parent c47a70e390
commit c8284170ad
17 changed files with 177 additions and 67 deletions

View File

@ -1,4 +1,32 @@
export default Ember.ArrayController.extend({ export default Ember.Controller.extend({
sortProperties: ['displayName'], badgeGroups: function(){
sortAscending: true var sorted = _.sortBy(this.get('model'), function(badge){
var pos = badge.get('badge_grouping.position');
var type = badge.get('badge_type_id');
var name = badge.get('displayName');
return ("000" + pos).slice(-4) + (10-type) + name;
});
var grouped = [];
var group = [], groupId;
sorted.forEach(function(badge){
if(groupId !== badge.badge_grouping_id){
if(group && group.length > 0){
grouped.push({badges: group, badgeGrouping: group[0].badge_grouping});
}
group = [];
groupId = badge.badge_grouping_id;
}
group.push(badge);
});
if(group && group.length > 0){
grouped.push({badges: group, badgeGrouping: group[0].badge_grouping});
}
return grouped;
}.property('model')
}); });

View File

@ -7,6 +7,23 @@
@module Discourse @module Discourse
**/ **/
export default Discourse.ObjectController.extend({ export default Discourse.ObjectController.extend({
actions: {
loadMore: function() {
var self = this;
var userBadges = this.get('userBadges');
Discourse.UserBadge.findByBadgeId(this.get('model.id'), {
offset: userBadges.length
}).then(function(userBadges) {
self.get('userBadges').pushObjects(userBadges);
if(userBadges.length === 0){
self.set('noMoreBadges', true);
}
});
}
},
layoutClass: function(){ layoutClass: function(){
var ub = this.get("userBadges"); var ub = this.get("userBadges");
if(ub && ub[0] && ub[0].post_id){ if(ub && ub[0] && ub[0].post_id){
@ -16,14 +33,15 @@ export default Discourse.ObjectController.extend({
} }
}.property("userBadges"), }.property("userBadges"),
grantDates: Em.computed.mapBy('userBadges', 'grantedAt'),
minGrantedAt: Em.computed.min('grantDates'),
canLoadMore: function() { canLoadMore: function() {
if(this.get('noMoreBadges')) {
return false;
}
if (this.get('userBadges')) { if (this.get('userBadges')) {
return this.get('model.grant_count') > this.get('userBadges.length'); return this.get('model.grant_count') > this.get('userBadges.length');
} else { } else {
return false; return false;
} }
}.property('model.grant_count', 'userBadges.length') }.property('noMoreBadges', 'model.grant_count', 'userBadges.length')
}); });

View File

@ -165,6 +165,13 @@ Discourse.Badge.reopenClass({
}); });
} }
var badgeGroupings = {};
if ('badge_groupings' in json) {
json.badge_groupings.forEach(function(badgeGroupingJson) {
badgeGroupings[badgeGroupingJson.id] = Discourse.BadgeGrouping.create(badgeGroupingJson);
});
}
// Create Badge objects. // Create Badge objects.
var badges = []; var badges = [];
if ("badge" in json) { if ("badge" in json) {
@ -175,8 +182,10 @@ Discourse.Badge.reopenClass({
badges = badges.map(function(badgeJson) { badges = badges.map(function(badgeJson) {
var badge = Discourse.Badge.create(badgeJson); var badge = Discourse.Badge.create(badgeJson);
badge.set('badge_type', badgeTypes[badge.get('badge_type_id')]); badge.set('badge_type', badgeTypes[badge.get('badge_type_id')]);
badge.set('badge_grouping', badgeGroupings[badge.get('badge_grouping_id')]);
return badge; return badge;
}); });
if ("badge" in json) { if ("badge" in json) {
return badges[0]; return badges[0];
} else { } else {

View File

@ -0,0 +1,10 @@
Discourse.BadgeGrouping= Discourse.Model.extend({
i18nNameKey: function() {
return this.get('name').toLowerCase().replace(/\s/g, '_');
}.property('name'),
displayName: function(){
var i18nKey = "badges.badge_grouping." + this.get('i18nNameKey') + ".name";
return I18n.t(i18nKey, {defaultValue: this.get('name')});
}.property()
});

View File

@ -111,11 +111,11 @@ Discourse.UserBadge.reopenClass({
**/ **/
findByBadgeId: function(badgeId, options) { findByBadgeId: function(badgeId, options) {
if (!options) { options = {}; } if (!options) { options = {}; }
var url = "/user_badges.json?badge_id=" + badgeId; options.badge_id = badgeId;
if (options.granted_before) {
url = url + "&granted_before=" + encodeURIComponent(options.granted_before); return Discourse.ajax("/user_badges.json", {
} data: options
return Discourse.ajax(url).then(function(json) { }).then(function(json) {
return Discourse.UserBadge.createFromJson(json); return Discourse.UserBadge.createFromJson(json);
}); });
}, },

View File

@ -28,16 +28,5 @@ Discourse.BadgesShowRoute = Ember.Route.extend({
}); });
controller.set('model', model); controller.set('model', model);
Discourse.set('title', model.get('displayName')); Discourse.set('title', model.get('displayName'));
},
actions: {
loadMore: function() {
var self = this;
Discourse.UserBadge.findByBadgeId(this.currentModel.get('id'), {
granted_before: this.get('controller.minGrantedAt') / 1000
}).then(function(userBadges) {
self.get('controller.userBadges').pushObjects(userBadges);
});
}
} }
}); });

View File

@ -3,7 +3,11 @@
<table class='badges-listing'> <table class='badges-listing'>
<tbody> <tbody>
{{#each}} {{#each badgeGroups}}
<tr class='title'>
<td colspan=4><h3>{{this.badgeGrouping.displayName}}</h3></td>
</tr>
{{#each this.badges}}
<tr> <tr>
<td class='granted'>{{#if this.has_badge}}<i class='fa fa-check'></i>{{/if}}</td> <td class='granted'>{{#if this.has_badge}}<i class='fa fa-check'></i>{{/if}}</td>
<td class='badge'>{{user-badge badge=this}}</td> <td class='badge'>{{user-badge badge=this}}</td>
@ -11,6 +15,7 @@
<td class='grant-count'>{{i18n badges.granted count=grant_count}}</td> <td class='grant-count'>{{i18n badges.granted count=grant_count}}</td>
</tr> </tr>
{{/each}} {{/each}}
{{/each}}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -75,6 +75,15 @@ table.badges-listing {
font-size: $base-font-size; font-size: $base-font-size;
} }
tr.title td {
padding-top: 30px;
padding-bottom: 15px;
}
tr.title {
border-top: 0px solid;
}
td { td {
padding: 10px 0px; padding: 10px 0px;
} }

View File

@ -3,13 +3,21 @@ class BadgesController < ApplicationController
def index def index
badges = Badge.all badges = Badge.all
badges = badges.where(enabled: true, listable: true) if(params[:only_listable] == "true") || !request.xhr?
badges = badges.includes(:badge_grouping).to_a if (params[:only_listable] == "true") || !request.xhr?
# NOTE: this is sorted client side if needed
badges = badges.includes(:badge_grouping)
.where(enabled: true, listable: true)
end
badges = badges.to_a
user_badges = nil user_badges = nil
if current_user if current_user
user_badges = Set.new(current_user.user_badges.select('distinct badge_id').pluck(:badge_id)) user_badges = Set.new(current_user.user_badges.select('distinct badge_id').pluck(:badge_id))
end end
serialized = MultiJson.dump(serialize_data(badges, BadgeSerializer, root: "badges", user_badges: user_badges, include_grouping: true)) serialized = MultiJson.dump(serialize_data(badges, BadgeIndexSerializer, root: "badges", user_badges: user_badges))
respond_to do |format| respond_to do |format|
format.html do format.html do
store_preloaded "badges", serialized store_preloaded "badges", serialized

View File

@ -7,11 +7,11 @@ class UserBadgesController < ApplicationController
user_badges = user.user_badges user_badges = user.user_badges
else else
badge = fetch_badge_from_params badge = fetch_badge_from_params
user_badges = badge.user_badges.order('granted_at DESC').limit(96) user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(96)
end end
if params[:granted_before] if offset = params[:offset]
user_badges = user_badges.where('granted_at < ?', Time.at(params[:granted_before].to_f)) user_badges = user_badges.offset(offset.to_i)
end end
user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic) user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic)

View File

@ -1,6 +1,4 @@
class Badge < ActiveRecord::Base class Badge < ActiveRecord::Base
belongs_to :badge_grouping
# badge ids # badge ids
Welcome = 5 Welcome = 5
NicePost = 6 NicePost = 6
@ -145,6 +143,8 @@ SQL
end end
belongs_to :badge_type belongs_to :badge_type
belongs_to :badge_grouping
has_many :user_badges, dependent: :destroy has_many :user_badges, dependent: :destroy
validates :name, presence: true, uniqueness: true validates :name, presence: true, uniqueness: true

View File

@ -1,12 +1,10 @@
class BadgeGrouping < ActiveRecord::Base class BadgeGrouping < ActiveRecord::Base
module Position GettingStarted = 1
GettingStarted = 10 Community = 2
Community = 11 Posting = 3
Posting = 12 TrustLevel = 4
TrustLevel = 13 Other = 5
Other = 14
end
has_many :badges has_many :badges
end end

View File

@ -1,3 +1,3 @@
class BadgeGroupingSerializer < ApplicationSerializer class BadgeGroupingSerializer < ApplicationSerializer
attributes :id, :name, :description attributes :id, :name, :description, :position
end end

View File

@ -0,0 +1,12 @@
class BadgeIndexSerializer < BadgeSerializer
attributes :has_badge
has_one :badge_grouping
def include_has_badge?
@options[:user_badges]
end
def has_badge
@options[:user_badges].include?(object.id)
end
end

View File

@ -1962,6 +1962,17 @@ en:
other: "%{count} granted" other: "%{count} granted"
select_badge_for_title: Select a badge to use as your title select_badge_for_title: Select a badge to use as your title
no_title: "<no title>" no_title: "<no title>"
badge_grouping:
getting_started:
name: Getting Started
community:
name: Community
trust_level:
name: Trust Level
other:
name: Other
posting:
name: Posting
badge: badge:
editor: editor:
name: Editor name: Editor

View File

@ -1,34 +1,42 @@
BadgeGrouping.seed do |g| BadgeGrouping.seed do |g|
g.id = 1 g.id = BadgeGrouping::GettingStarted
g.name = "Getting Started" g.name = "Getting Started"
g.position = BadgeGrouping::Position::GettingStarted g.position = 10
end end
BadgeGrouping.seed do |g| BadgeGrouping.seed do |g|
g.id = 2 g.id = BadgeGrouping::Community
g.name = "Community" g.name = "Community"
g.position = BadgeGrouping::Position::Community g.position = 11
end end
BadgeGrouping.seed do |g| BadgeGrouping.seed do |g|
g.id = 3 g.id = BadgeGrouping::Posting
g.name = "Posting" g.name = "Posting"
g.position = BadgeGrouping::Position::Posting g.position = 12
end end
BadgeGrouping.seed do |g| BadgeGrouping.seed do |g|
g.id = 4 g.id = BadgeGrouping::TrustLevel
g.name = "Trust Level" g.name = "Trust Level"
g.position = BadgeGrouping::Position::TrustLevel g.position = 13
end end
BadgeGrouping.seed do |g| BadgeGrouping.seed do |g|
g.id = 5 g.id = BadgeGrouping::Other
g.name = "Other" g.name = "Other"
g.position = BadgeGrouping::Position::Other g.position = 14
end end
# BUGFIX
Badge.exec_sql 'UPDATE badges
SET badge_grouping_id = NULL
WHERE NOT EXISTS (
SELECT 1 FROM badge_groupings g
WHERE g.id = badge_grouping_id
)'
# Trust level system badges. # Trust level system badges.
trust_level_badges = [ trust_level_badges = [
{id: 1, name: "Basic User", type: BadgeType::Bronze}, {id: 1, name: "Basic User", type: BadgeType::Bronze},
@ -43,7 +51,7 @@ trust_level_badges.each do |spec|
b.default_name = spec[:name] b.default_name = spec[:name]
b.badge_type_id = spec[:type] b.badge_type_id = spec[:type]
b.query = Badge::Queries.trust_level(spec[:id]) b.query = Badge::Queries.trust_level(spec[:id])
b.default_badge_grouping_id = BadgeGrouping::Position::TrustLevel b.default_badge_grouping_id = BadgeGrouping::TrustLevel
# allow title for leader and elder # allow title for leader and elder
b.allow_title = spec[:id] > 2 b.allow_title = spec[:id] > 2
@ -57,7 +65,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = false b.target_posts = false
b.query = Badge::Queries::Reader b.query = Badge::Queries::Reader
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
b.auto_revoke = false b.auto_revoke = false
end end
@ -68,7 +76,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = false b.target_posts = false
b.query = Badge::Queries::ReadGuidelines b.query = Badge::Queries::ReadGuidelines
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -78,7 +86,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = true b.target_posts = true
b.query = Badge::Queries::FirstLink b.query = Badge::Queries::FirstLink
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -88,7 +96,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = true b.target_posts = true
b.query = Badge::Queries::FirstQuote b.query = Badge::Queries::FirstQuote
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -98,7 +106,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = true b.target_posts = true
b.query = Badge::Queries::FirstLike b.query = Badge::Queries::FirstLike
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -108,7 +116,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = false b.target_posts = false
b.query = Badge::Queries::FirstFlag b.query = Badge::Queries::FirstFlag
b.default_badge_grouping_id = BadgeGrouping::Position::Community b.default_badge_grouping_id = BadgeGrouping::Community
end end
Badge.seed do |b| Badge.seed do |b|
@ -118,7 +126,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = true b.target_posts = true
b.query = Badge::Queries::FirstShare b.query = Badge::Queries::FirstShare
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -128,7 +136,7 @@ Badge.seed do |b|
b.multiple_grant = false b.multiple_grant = false
b.target_posts = true b.target_posts = true
b.query = Badge::Queries::Welcome b.query = Badge::Queries::Welcome
b.default_badge_grouping_id = BadgeGrouping::Position::Community b.default_badge_grouping_id = BadgeGrouping::Community
end end
Badge.seed do |b| Badge.seed do |b|
@ -137,7 +145,7 @@ Badge.seed do |b|
b.badge_type_id = BadgeType::Bronze b.badge_type_id = BadgeType::Bronze
b.multiple_grant = false b.multiple_grant = false
b.query = Badge::Queries::Autobiographer b.query = Badge::Queries::Autobiographer
b.default_badge_grouping_id = BadgeGrouping::Position::GettingStarted b.default_badge_grouping_id = BadgeGrouping::GettingStarted
end end
Badge.seed do |b| Badge.seed do |b|
@ -146,7 +154,7 @@ Badge.seed do |b|
b.badge_type_id = BadgeType::Bronze b.badge_type_id = BadgeType::Bronze
b.multiple_grant = false b.multiple_grant = false
b.query = Badge::Queries::Editor b.query = Badge::Queries::Editor
b.default_badge_grouping_id = BadgeGrouping::Position::Community b.default_badge_grouping_id = BadgeGrouping::Community
end end
# #
@ -165,6 +173,6 @@ like_badges.each do |spec|
b.multiple_grant = spec[:multiple] b.multiple_grant = spec[:multiple]
b.target_posts = true b.target_posts = true
b.query = Badge::Queries.like_badge(Badge.like_badge_counts[spec[:id]]) b.query = Badge::Queries.like_badge(Badge.like_badge_counts[spec[:id]])
b.default_badge_grouping_id = BadgeGrouping::Position::Posting b.default_badge_grouping_id = BadgeGrouping::Posting
end end
end end

View File

@ -0,0 +1,5 @@
class SetDefaultBadgeGrouping < ActiveRecord::Migration
def change
change_column :badges, :badge_grouping_id, :integer, null: false, default: 5
end
end