FIX: Many bugs with admin badges interface

* Editing a badge's title would show it as changed in the side even if
  you didn't hit save

* Clicking a badge would not scroll to the top

* If there was an error saving a badge there was a missing i18n key

* URLs were using queryParams instead of paths

* User `label` tags for checkboxes for larger click targets

* Saved! text would persist when viewing another badge

* After creating a new badge it would show nothing

* Validation errors were not being properly released to the client

* Query errors were surrounded by an extra array
This commit is contained in:
Robin Ward
2014-10-17 14:27:40 -04:00
parent ab9a0235b4
commit 0cbdf6f5bb
17 changed files with 359 additions and 495 deletions
@@ -1,37 +0,0 @@
import ObjectController from 'discourse/controllers/object';
/**
This is the itemController for `Discourse.AdminBadgesController`. Its main purpose
is to indicate which badge was selected.
@class AdminBadgeController
@extends ObjectController
@namespace Discourse
@module Discourse
**/
export default ObjectController.extend({
/**
Whether this badge has been selected.
@property selected
@type {Boolean}
**/
selected: Discourse.computed.propertyEqual('model.name', 'parentController.selectedItem.name'),
/**
Show the displayName only if it is different from the name.
@property showDisplayName
@type {Boolean}
**/
showDisplayName: Discourse.computed.propertyNotEqual('selectedItem.name', 'selectedItem.displayName'),
/**
Don't allow editing if this is a system badge.
@property readOnly
@type {Boolean}
**/
readOnly: Ember.computed.alias('model.system')
});
@@ -0,0 +1,98 @@
import BufferedContent from 'discourse/mixins/buffered-content';
export default Ember.ObjectController.extend(BufferedContent, {
needs: ['admin-badges'],
saving: false,
savingStatus: '',
badgeTypes: Em.computed.alias('controllers.admin-badges.badgeTypes'),
badgeGroupings: Em.computed.alias('controllers.admin-badges.badgeGroupings'),
badgeTriggers: Em.computed.alias('controllers.admin-badges.badgeTriggers'),
protectedSystemFields: Em.computed.alias('controllers.admin-badges.protectedSystemFields'),
readOnly: Ember.computed.alias('buffered.system'),
showDisplayName: Discourse.computed.propertyNotEqual('name', 'displayName'),
canEditDescription: Em.computed.none('buffered.translatedDescription'),
_resetSaving: function() {
this.set('saving', false);
this.set('savingStatus', '');
}.observes('model.id'),
actions: {
save: function() {
if (!this.get('saving')) {
var fields = ['allow_title', 'multiple_grant',
'listable', 'auto_revoke',
'enabled', 'show_posts',
'target_posts', 'name', 'description',
'icon', 'query', 'badge_grouping_id',
'trigger', 'badge_type_id'],
self = this;
if (this.get('buffered.system')){
var protectedFields = this.get('protectedSystemFields');
fields = _.filter(fields, function(f){
return !_.include(protectedFields,f);
});
}
this.set('saving', true);
this.set('savingStatus', I18n.t('saving'));
var boolFields = ['allow_title', 'multiple_grant',
'listable', 'auto_revoke',
'enabled', 'show_posts',
'target_posts' ];
var data = {},
buffered = this.get('buffered');
fields.forEach(function(field){
var d = buffered.get(field);
if (_.include(boolFields, field)) { d = !!d; }
data[field] = d;
});
var newBadge = !this.get('id'),
model = this.get('model');
this.get('model').save(data).then(function() {
if (newBadge) {
self.get('controllers.admin-badges').pushObject(model);
self.transitionToRoute('adminBadges.show', model.get('id'));
} else {
self.commitBuffer();
self.set('savingStatus', I18n.t('saved'));
}
}).catch(function(error) {
self.set('savingStatus', I18n.t('failed'));
self.send('saveError', error);
}).finally(function() {
self.set('saving', false);
});
}
},
destroy: function() {
var self = this,
adminBadgesController = this.get('controllers.admin-badges'),
model = this.get('model');
if (!model.get('id')) {
self.transitionToRoute('adminBadges.index');
return;
}
return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
model.destroy().then(function() {
adminBadgesController.removeObject(model);
self.transitionToRoute('adminBadges.index');
}).catch(function() {
bootbox.alert(I18n.t('generic_error'));
});
}
});
}
}
});
@@ -1,165 +1 @@
/**
This controller supports the interface for dealing with badges.
@class AdminBadgesController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend({
needs: ['modal'],
itemController: 'admin-badge',
queryParams: ['badgeId'],
badgeId: Em.computed.alias('selectedId'),
/**
ID of the currently selected badge.
@property selectedId
@type {Integer}
**/
selectedId: null,
/**
Badge that is currently selected.
@property selectedItem
@type {Discourse.Badge}
**/
selectedItem: function() {
if (this.get('selectedId') === undefined || this.get('selectedId') === "undefined") {
// New Badge
return this.get('newBadge');
} else {
// Existing Badge
var selectedId = parseInt(this.get('selectedId'));
return this.get('model').filter(function(badge) {
return parseInt(badge.get('id')) === selectedId;
})[0];
}
}.property('selectedId', 'newBadge'),
/**
Unsaved badge, if one exists.
@property newBadge
@type {Discourse.Badge}
**/
newBadge: function() {
return this.get('model').filter(function(badge) {
return badge.get('id') === undefined;
})[0];
}.property('model.@each.id'),
/**
Whether a new unsaved badge exists.
@property newBadgeExists
@type {Discourse.Badge}
**/
newBadgeExists: Em.computed.notEmpty('newBadge'),
/**
We don't allow setting a description if a translation for the given badge
name exists.
@property canEditDescription
@type {Boolean}
**/
canEditDescription: Em.computed.none('selectedItem.translatedDescription'),
/**
Disable saving if the currently selected item is being saved.
@property disableSave
@type {Boolean}
**/
disableSave: Em.computed.alias('selectedItem.saving'),
actions: {
/**
Create a new badge and select it.
@method newBadge
**/
createNewBadge: function() {
var badge = Discourse.Badge.create({
name: I18n.t('admin.badges.new_badge')
});
this.pushObject(badge);
this.send('selectBadge', badge);
},
/**
Select a particular badge.
@method selectBadge
@param {Discourse.Badge} badge The badge to be selected
**/
selectBadge: function(badge) {
this.set('selectedId', badge.get('id'));
},
/**
Save the selected badge.
@method save
**/
save: function() {
if (!this.get('disableSave')) {
var fields = ['allow_title', 'multiple_grant',
'listable', 'auto_revoke',
'enabled', 'show_posts',
'target_posts', 'name', 'description',
'icon', 'query', 'badge_grouping_id',
'trigger', 'badge_type_id'],
self = this;
if (this.get('selectedItem.system')){
var protectedFields = this.get('protectedSystemFields');
fields = _.filter(fields, function(f){
return !_.include(protectedFields,f);
});
}
this.get('selectedItem').save(fields).catch(function(error) {
// this shows the admin-badge-preview modal with the error
// kinda weird, but it consolidates the display logic for badge errors
self.send('saveError', error);
});
}
},
/**
Confirm before destroying the selected badge.
@method destroy
**/
destroy: function() {
// Delete immediately if the selected badge is new.
if (!this.get('selectedItem.id')) {
this.get('model').removeObject(this.get('selectedItem'));
this.set('selectedId', null);
return;
}
var self = this;
return bootbox.confirm(I18n.t("admin.badges.delete_confirm"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
if (result) {
var selected = self.get('selectedItem');
selected.destroy().then(function() {
// Success.
self.set('selectedId', null);
self.get('model').removeObject(selected);
}, function() {
// Failure.
bootbox.alert(I18n.t('generic_error'));
});
}
});
}
}
});
export default Ember.ArrayController.extend();