mirror of
https://github.com/discourse/discourse.git
synced 2025-07-05 00:32:27 +00:00
DEV: Update /admin/badges
to modern Ember patterns (#17672)
* Nest admin badges controller and route files * Use standard file names for admin-badges * Update resolver to allow standardized file structure for admin * Add adminBadges.index controller for property tracking * Modernize admin badges controller * Modernize admin-badges route * Add admin-badges index route * Modernize admin-badges.show controller and route * Modernize admin-badges.award controller and route * Convert BadgeButton to a Glimmer component
This commit is contained in:
parent
36446649ff
commit
40cb46631f
@ -1,99 +0,0 @@
|
|||||||
import Controller from "@ember/controller";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import bootbox from "bootbox";
|
|
||||||
import { extractError } from "discourse/lib/ajax-error";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
|
|
||||||
export default Controller.extend({
|
|
||||||
saving: false,
|
|
||||||
replaceBadgeOwners: false,
|
|
||||||
grantExistingHolders: false,
|
|
||||||
fileSelected: false,
|
|
||||||
unmatchedEntries: null,
|
|
||||||
resultsMessage: null,
|
|
||||||
success: false,
|
|
||||||
unmatchedEntriesCount: 0,
|
|
||||||
|
|
||||||
resetState() {
|
|
||||||
this.setProperties({
|
|
||||||
saving: false,
|
|
||||||
unmatchedEntries: null,
|
|
||||||
resultsMessage: null,
|
|
||||||
success: false,
|
|
||||||
unmatchedEntriesCount: 0,
|
|
||||||
});
|
|
||||||
this.send("updateFileSelected");
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("fileSelected", "saving")
|
|
||||||
massAwardButtonDisabled(fileSelected, saving) {
|
|
||||||
return !fileSelected || saving;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("unmatchedEntriesCount", "unmatchedEntries.length")
|
|
||||||
unmatchedEntriesTruncated(unmatchedEntriesCount, length) {
|
|
||||||
return unmatchedEntriesCount && length && unmatchedEntriesCount > length;
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
updateFileSelected() {
|
|
||||||
this.set(
|
|
||||||
"fileSelected",
|
|
||||||
!!document.querySelector("#massAwardCSVUpload")?.files?.length
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
massAward() {
|
|
||||||
const file = document.querySelector("#massAwardCSVUpload").files[0];
|
|
||||||
|
|
||||||
if (this.model && file) {
|
|
||||||
const options = {
|
|
||||||
type: "POST",
|
|
||||||
processData: false,
|
|
||||||
contentType: false,
|
|
||||||
data: new FormData(),
|
|
||||||
};
|
|
||||||
|
|
||||||
options.data.append("file", file);
|
|
||||||
options.data.append("replace_badge_owners", this.replaceBadgeOwners);
|
|
||||||
options.data.append("grant_existing_holders", this.grantExistingHolders);
|
|
||||||
|
|
||||||
this.resetState();
|
|
||||||
this.set("saving", true);
|
|
||||||
|
|
||||||
ajax(`/admin/badges/award/${this.model.id}`, options)
|
|
||||||
.then(
|
|
||||||
({
|
|
||||||
matched_users_count: matchedCount,
|
|
||||||
unmatched_entries: unmatchedEntries,
|
|
||||||
unmatched_entries_count: unmatchedEntriesCount,
|
|
||||||
}) => {
|
|
||||||
this.setProperties({
|
|
||||||
resultsMessage: I18n.t("admin.badges.mass_award.success", {
|
|
||||||
count: matchedCount,
|
|
||||||
}),
|
|
||||||
success: true,
|
|
||||||
});
|
|
||||||
if (unmatchedEntries.length) {
|
|
||||||
this.setProperties({
|
|
||||||
unmatchedEntries,
|
|
||||||
unmatchedEntriesCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
this.setProperties({
|
|
||||||
resultsMessage: extractError(error),
|
|
||||||
success: false,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => this.set("saving", false));
|
|
||||||
} else {
|
|
||||||
bootbox.alert(I18n.t("admin.badges.mass_award.aborted"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,216 +0,0 @@
|
|||||||
import Controller, { inject as controller } from "@ember/controller";
|
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import bootbox from "bootbox";
|
|
||||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
import { propertyNotEqual } from "discourse/lib/computed";
|
|
||||||
import { equal, reads } from "@ember/object/computed";
|
|
||||||
import { next } from "@ember/runloop";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import getURL from "discourse-common/lib/get-url";
|
|
||||||
|
|
||||||
const IMAGE = "image";
|
|
||||||
const ICON = "icon";
|
|
||||||
|
|
||||||
export default Controller.extend(bufferedProperty("model"), {
|
|
||||||
adminBadges: controller(),
|
|
||||||
saving: false,
|
|
||||||
savingStatus: "",
|
|
||||||
selectedGraphicType: null,
|
|
||||||
badgeTypes: reads("adminBadges.badgeTypes"),
|
|
||||||
badgeGroupings: reads("adminBadges.badgeGroupings"),
|
|
||||||
badgeTriggers: reads("adminBadges.badgeTriggers"),
|
|
||||||
protectedSystemFields: reads("adminBadges.protectedSystemFields"),
|
|
||||||
readOnly: reads("buffered.system"),
|
|
||||||
showDisplayName: propertyNotEqual("name", "displayName"),
|
|
||||||
iconSelectorSelected: equal("selectedGraphicType", ICON),
|
|
||||||
imageUploaderSelected: equal("selectedGraphicType", IMAGE),
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
// this is needed because the model doesnt have default values
|
|
||||||
// and as we are using a bufferedProperty it's not accessible
|
|
||||||
// in any other way
|
|
||||||
next(() => {
|
|
||||||
if (this.model) {
|
|
||||||
if (!this.model.badge_type_id) {
|
|
||||||
this.model.set(
|
|
||||||
"badge_type_id",
|
|
||||||
this.get("badgeTypes.firstObject.id")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.model.badge_grouping_id) {
|
|
||||||
this.model.set(
|
|
||||||
"badge_grouping_id",
|
|
||||||
this.get("badgeGroupings.firstObject.id")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.model.trigger) {
|
|
||||||
this.model.set("trigger", this.get("badgeTriggers.firstObject.id"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("model.query", "buffered.query")
|
|
||||||
hasQuery(modelQuery, bufferedQuery) {
|
|
||||||
if (bufferedQuery) {
|
|
||||||
return bufferedQuery.trim().length > 0;
|
|
||||||
}
|
|
||||||
return modelQuery && modelQuery.trim().length > 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("model.i18n_name")
|
|
||||||
textCustomizationPrefix(i18n_name) {
|
|
||||||
return `badges.${i18n_name}.`;
|
|
||||||
},
|
|
||||||
|
|
||||||
@observes("model.id")
|
|
||||||
_resetSaving() {
|
|
||||||
this.set("saving", false);
|
|
||||||
this.set("savingStatus", "");
|
|
||||||
},
|
|
||||||
|
|
||||||
showIconSelector() {
|
|
||||||
this.set("selectedGraphicType", ICON);
|
|
||||||
},
|
|
||||||
|
|
||||||
showImageUploader() {
|
|
||||||
this.set("selectedGraphicType", IMAGE);
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
changeGraphicType(newType) {
|
|
||||||
if (newType === IMAGE) {
|
|
||||||
this.showImageUploader();
|
|
||||||
} else if (newType === ICON) {
|
|
||||||
this.showIconSelector();
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown badge graphic type "${newType}"`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
setImage(upload) {
|
|
||||||
this.buffered.setProperties({
|
|
||||||
image_upload_id: upload.id,
|
|
||||||
image_url: getURL(upload.url),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@action
|
|
||||||
removeImage() {
|
|
||||||
this.buffered.setProperties({
|
|
||||||
image_upload_id: null,
|
|
||||||
image_url: null,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
save() {
|
|
||||||
if (!this.saving) {
|
|
||||||
let fields = [
|
|
||||||
"allow_title",
|
|
||||||
"multiple_grant",
|
|
||||||
"listable",
|
|
||||||
"auto_revoke",
|
|
||||||
"enabled",
|
|
||||||
"show_posts",
|
|
||||||
"target_posts",
|
|
||||||
"name",
|
|
||||||
"description",
|
|
||||||
"long_description",
|
|
||||||
"icon",
|
|
||||||
"image_upload_id",
|
|
||||||
"query",
|
|
||||||
"badge_grouping_id",
|
|
||||||
"trigger",
|
|
||||||
"badge_type_id",
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.get("buffered.system")) {
|
|
||||||
let protectedFields = this.protectedSystemFields || [];
|
|
||||||
fields = fields.filter((f) => !protectedFields.includes(f));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("saving", true);
|
|
||||||
this.set("savingStatus", I18n.t("saving"));
|
|
||||||
|
|
||||||
const boolFields = [
|
|
||||||
"allow_title",
|
|
||||||
"multiple_grant",
|
|
||||||
"listable",
|
|
||||||
"auto_revoke",
|
|
||||||
"enabled",
|
|
||||||
"show_posts",
|
|
||||||
"target_posts",
|
|
||||||
];
|
|
||||||
|
|
||||||
const data = {};
|
|
||||||
const buffered = this.buffered;
|
|
||||||
fields.forEach(function (field) {
|
|
||||||
let d = buffered.get(field);
|
|
||||||
if (boolFields.includes(field)) {
|
|
||||||
d = !!d;
|
|
||||||
}
|
|
||||||
data[field] = d;
|
|
||||||
});
|
|
||||||
|
|
||||||
const newBadge = !this.id;
|
|
||||||
const model = this.model;
|
|
||||||
this.model
|
|
||||||
.save(data)
|
|
||||||
.then(() => {
|
|
||||||
if (newBadge) {
|
|
||||||
const adminBadges = this.get("adminBadges.model");
|
|
||||||
if (!adminBadges.includes(model)) {
|
|
||||||
adminBadges.pushObject(model);
|
|
||||||
}
|
|
||||||
this.transitionToRoute("adminBadges.show", model.get("id"));
|
|
||||||
} else {
|
|
||||||
this.commitBuffer();
|
|
||||||
this.set("savingStatus", I18n.t("saved"));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(popupAjaxError)
|
|
||||||
.finally(() => {
|
|
||||||
this.set("saving", false);
|
|
||||||
this.set("savingStatus", "");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
const adminBadges = this.get("adminBadges.model");
|
|
||||||
const model = this.model;
|
|
||||||
|
|
||||||
if (!model.get("id")) {
|
|
||||||
this.transitionToRoute("adminBadges.index");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bootbox.confirm(
|
|
||||||
I18n.t("admin.badges.delete_confirm"),
|
|
||||||
I18n.t("no_value"),
|
|
||||||
I18n.t("yes_value"),
|
|
||||||
(result) => {
|
|
||||||
if (result) {
|
|
||||||
model
|
|
||||||
.destroy()
|
|
||||||
.then(() => {
|
|
||||||
adminBadges.removeObject(model);
|
|
||||||
this.transitionToRoute("adminBadges.index");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
bootbox.alert(I18n.t("generic_error"));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,18 +1,23 @@
|
|||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
export default Controller.extend({
|
export default class AdminBadgesController extends Controller {
|
||||||
routing: service("-routing"),
|
@service router;
|
||||||
|
|
||||||
@discourseComputed("routing.currentRouteName")
|
// Set by the route
|
||||||
selectedRoute() {
|
@tracked badgeGroupings;
|
||||||
const currentRoute = this.routing.currentRouteName;
|
@tracked badgeTypes;
|
||||||
|
@tracked protectedSystemFields;
|
||||||
|
@tracked badgeTriggers;
|
||||||
|
|
||||||
|
get selectedRoute() {
|
||||||
|
const currentRoute = this.router.currentRouteName;
|
||||||
const indexRoute = "adminBadges.index";
|
const indexRoute = "adminBadges.index";
|
||||||
if (currentRoute === indexRoute) {
|
if (currentRoute === indexRoute) {
|
||||||
return "adminBadges.show";
|
return "adminBadges.show";
|
||||||
} else {
|
} else {
|
||||||
return this.routing.currentRouteName;
|
return currentRoute;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
import Controller from "@ember/controller";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import bootbox from "bootbox";
|
||||||
|
import { extractError } from "discourse/lib/ajax-error";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class AdminBadgesAwardController extends Controller {
|
||||||
|
@tracked saving = false;
|
||||||
|
@tracked replaceBadgeOwners = false;
|
||||||
|
@tracked grantExistingHolders = false;
|
||||||
|
@tracked fileSelected = false;
|
||||||
|
@tracked unmatchedEntries = null;
|
||||||
|
@tracked resultsMessage = null;
|
||||||
|
@tracked success = false;
|
||||||
|
@tracked unmatchedEntriesCount = 0;
|
||||||
|
|
||||||
|
resetState() {
|
||||||
|
this.saving = false;
|
||||||
|
this.unmatchedEntries = null;
|
||||||
|
this.resultsMessage = null;
|
||||||
|
this.success = false;
|
||||||
|
this.unmatchedEntriesCount = 0;
|
||||||
|
|
||||||
|
this.updateFileSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
get massAwardButtonDisabled() {
|
||||||
|
return !this.fileSelected || this.saving;
|
||||||
|
}
|
||||||
|
|
||||||
|
get unmatchedEntriesTruncated() {
|
||||||
|
let count = this.unmatchedEntriesCount;
|
||||||
|
let length = this.unmatchedEntries.length;
|
||||||
|
return count && length && count > length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateFileSelected() {
|
||||||
|
this.fileSelected = !!document.querySelector("#massAwardCSVUpload")?.files
|
||||||
|
?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
massAward() {
|
||||||
|
const file = document.querySelector("#massAwardCSVUpload").files[0];
|
||||||
|
|
||||||
|
if (this.model && file) {
|
||||||
|
const options = {
|
||||||
|
type: "POST",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
data: new FormData(),
|
||||||
|
};
|
||||||
|
|
||||||
|
options.data.append("file", file);
|
||||||
|
options.data.append("replace_badge_owners", this.replaceBadgeOwners);
|
||||||
|
options.data.append("grant_existing_holders", this.grantExistingHolders);
|
||||||
|
|
||||||
|
this.resetState();
|
||||||
|
this.saving = true;
|
||||||
|
|
||||||
|
ajax(`/admin/badges/award/${this.model.id}`, options)
|
||||||
|
.then(
|
||||||
|
({
|
||||||
|
matched_users_count: matchedCount,
|
||||||
|
unmatched_entries: unmatchedEntries,
|
||||||
|
unmatched_entries_count: unmatchedEntriesCount,
|
||||||
|
}) => {
|
||||||
|
this.resultsMessage = I18n.t("admin.badges.mass_award.success", {
|
||||||
|
count: matchedCount,
|
||||||
|
});
|
||||||
|
this.success = true;
|
||||||
|
if (unmatchedEntries.length) {
|
||||||
|
this.unmatchedEntries = unmatchedEntries;
|
||||||
|
this.unmatchedEntriesCount = unmatchedEntriesCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
this.resultsMessage = extractError(error);
|
||||||
|
this.success = false;
|
||||||
|
})
|
||||||
|
.finally(() => (this.saving = false));
|
||||||
|
} else {
|
||||||
|
bootbox.alert(I18n.t("admin.badges.mass_award.aborted"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import Controller from "@ember/controller";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class AdminBadgesIndexController extends Controller {
|
||||||
|
// Set by the route
|
||||||
|
@tracked badgeIntroLinks;
|
||||||
|
@tracked badgeIntroEmoji;
|
||||||
|
}
|
@ -0,0 +1,238 @@
|
|||||||
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import bootbox from "bootbox";
|
||||||
|
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { next } from "@ember/runloop";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import getURL from "discourse-common/lib/get-url";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
const IMAGE = "image";
|
||||||
|
const ICON = "icon";
|
||||||
|
|
||||||
|
// TODO: Stop using Mixin here
|
||||||
|
export default class AdminBadgesShowController extends Controller.extend(
|
||||||
|
bufferedProperty("model")
|
||||||
|
) {
|
||||||
|
@controller adminBadges;
|
||||||
|
@service router;
|
||||||
|
|
||||||
|
@tracked saving = false;
|
||||||
|
@tracked savingStatus = "";
|
||||||
|
@tracked selectedGraphicType = null;
|
||||||
|
|
||||||
|
get badgeTypes() {
|
||||||
|
return this.adminBadges.badgeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get badgeGroupings() {
|
||||||
|
return this.adminBadges.badgeGroupings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get badgeTriggers() {
|
||||||
|
return this.adminBadges.badgeTriggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
get protectedSystemFields() {
|
||||||
|
return this.adminBadges.protectedSystemFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
get readOnly() {
|
||||||
|
return this.buffered.get("system");
|
||||||
|
}
|
||||||
|
|
||||||
|
get showDisplayName() {
|
||||||
|
return this.name !== this.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get iconSelectorSelected() {
|
||||||
|
return this.selectedGraphicType === ICON;
|
||||||
|
}
|
||||||
|
|
||||||
|
get imageUploaderSelected() {
|
||||||
|
return this.selectedGraphicType === IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
super.init(...arguments);
|
||||||
|
|
||||||
|
// this is needed because the model doesnt have default values
|
||||||
|
// and as we are using a bufferedProperty it's not accessible
|
||||||
|
// in any other way
|
||||||
|
next(() => {
|
||||||
|
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
|
||||||
|
if (this.model) {
|
||||||
|
if (!this.model.badge_type_id) {
|
||||||
|
this.model.set("badge_type_id", this.badgeTypes?.[0]?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.model.badge_grouping_id) {
|
||||||
|
this.model.set("badge_grouping_id", this.badgeGroupings?.[0]?.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.model.trigger) {
|
||||||
|
this.model.set("trigger", this.badgeTriggers?.[0]?.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasQuery() {
|
||||||
|
let modelQuery = this.model.query;
|
||||||
|
let bufferedQuery = this.bufferedQuery;
|
||||||
|
|
||||||
|
if (bufferedQuery) {
|
||||||
|
return bufferedQuery.trim().length > 0;
|
||||||
|
}
|
||||||
|
return modelQuery && modelQuery.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get textCustomizationPrefix() {
|
||||||
|
return `badges.${this.model.i18n_name}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Remove observer
|
||||||
|
@observes("model.id")
|
||||||
|
_resetSaving() {
|
||||||
|
this.saving = false;
|
||||||
|
this.savingStatus = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
showIconSelector() {
|
||||||
|
this.selectedGraphicType = ICON;
|
||||||
|
}
|
||||||
|
|
||||||
|
showImageUploader() {
|
||||||
|
this.selectedGraphicType = IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
changeGraphicType(newType) {
|
||||||
|
if (newType === IMAGE) {
|
||||||
|
this.showImageUploader();
|
||||||
|
} else if (newType === ICON) {
|
||||||
|
this.showIconSelector();
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown badge graphic type "${newType}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setImage(upload) {
|
||||||
|
this.buffered.set("image_upload_id", upload.id);
|
||||||
|
this.buffered.set("image_url", getURL(upload.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
removeImage() {
|
||||||
|
this.buffered.set("image_upload_id", null);
|
||||||
|
this.buffered.set("image_url", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
save() {
|
||||||
|
if (!this.saving) {
|
||||||
|
let fields = [
|
||||||
|
"allow_title",
|
||||||
|
"multiple_grant",
|
||||||
|
"listable",
|
||||||
|
"auto_revoke",
|
||||||
|
"enabled",
|
||||||
|
"show_posts",
|
||||||
|
"target_posts",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"long_description",
|
||||||
|
"icon",
|
||||||
|
"image_upload_id",
|
||||||
|
"query",
|
||||||
|
"badge_grouping_id",
|
||||||
|
"trigger",
|
||||||
|
"badge_type_id",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.buffered.get("system")) {
|
||||||
|
let protectedFields = this.protectedSystemFields || [];
|
||||||
|
fields = fields.filter((f) => !protectedFields.includes(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saving = true;
|
||||||
|
this.savingStatus = I18n.t("saving");
|
||||||
|
|
||||||
|
const boolFields = [
|
||||||
|
"allow_title",
|
||||||
|
"multiple_grant",
|
||||||
|
"listable",
|
||||||
|
"auto_revoke",
|
||||||
|
"enabled",
|
||||||
|
"show_posts",
|
||||||
|
"target_posts",
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = {};
|
||||||
|
const buffered = this.buffered;
|
||||||
|
fields.forEach(function (field) {
|
||||||
|
let d = buffered.get(field);
|
||||||
|
if (boolFields.includes(field)) {
|
||||||
|
d = !!d;
|
||||||
|
}
|
||||||
|
data[field] = d;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newBadge = !this.id;
|
||||||
|
const model = this.model;
|
||||||
|
this.model
|
||||||
|
.save(data)
|
||||||
|
.then(() => {
|
||||||
|
if (newBadge) {
|
||||||
|
const adminBadges = this.get("adminBadges.model");
|
||||||
|
if (!adminBadges.includes(model)) {
|
||||||
|
adminBadges.pushObject(model);
|
||||||
|
}
|
||||||
|
this.transitionToRoute("adminBadges.show", model.get("id"));
|
||||||
|
} else {
|
||||||
|
this.commitBuffer();
|
||||||
|
this.savingStatus = I18n.t("saved");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(popupAjaxError)
|
||||||
|
.finally(() => {
|
||||||
|
this.saving = false;
|
||||||
|
this.savingStatus = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
destroyBadge() {
|
||||||
|
const adminBadges = this.adminBadges.model;
|
||||||
|
const model = this.model;
|
||||||
|
|
||||||
|
if (!model?.get("id")) {
|
||||||
|
this.router.transitionTo("adminBadges.index");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bootbox.confirm(
|
||||||
|
I18n.t("admin.badges.delete_confirm"),
|
||||||
|
I18n.t("no_value"),
|
||||||
|
I18n.t("yes_value"),
|
||||||
|
(result) => {
|
||||||
|
if (result) {
|
||||||
|
model
|
||||||
|
.destroy()
|
||||||
|
.then(() => {
|
||||||
|
adminBadges.removeObject(model);
|
||||||
|
this.transitionToRoute("adminBadges.index");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
bootbox.alert(I18n.t("generic_error"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
import Badge from "discourse/models/badge";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import Route from "@ember/routing/route";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import bootbox from "bootbox";
|
|
||||||
import { get } from "@ember/object";
|
|
||||||
import showModal from "discourse/lib/show-modal";
|
|
||||||
|
|
||||||
export default Route.extend({
|
|
||||||
serialize(m) {
|
|
||||||
return { badge_id: get(m, "id") || "new" };
|
|
||||||
},
|
|
||||||
|
|
||||||
model(params) {
|
|
||||||
if (params.badge_id === "new") {
|
|
||||||
return Badge.create({
|
|
||||||
name: I18n.t("admin.badges.new_badge"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this.modelFor("adminBadges").findBy(
|
|
||||||
"id",
|
|
||||||
parseInt(params.badge_id, 10)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
this._super(...arguments);
|
|
||||||
if (model.image_url) {
|
|
||||||
controller.showImageUploader();
|
|
||||||
} else if (model.icon) {
|
|
||||||
controller.showIconSelector();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
editGroupings() {
|
|
||||||
const model = this.controllerFor("admin-badges").get("badgeGroupings");
|
|
||||||
showModal("admin-edit-badge-groupings", { model, admin: true });
|
|
||||||
},
|
|
||||||
|
|
||||||
preview(badge, explain) {
|
|
||||||
badge.set("preview_loading", true);
|
|
||||||
ajax("/admin/badges/preview.json", {
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
sql: badge.get("query"),
|
|
||||||
target_posts: !!badge.get("target_posts"),
|
|
||||||
trigger: badge.get("trigger"),
|
|
||||||
explain,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(function (model) {
|
|
||||||
badge.set("preview_loading", false);
|
|
||||||
showModal("admin-badge-preview", { model, admin: true });
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
badge.set("preview_loading", false);
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(error);
|
|
||||||
bootbox.alert("Network error");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@ -4,15 +4,14 @@ import DiscourseRoute from "discourse/routes/discourse";
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
export default DiscourseRoute.extend({
|
export default class AdminBadgesRoute extends DiscourseRoute {
|
||||||
_json: null,
|
_json = null;
|
||||||
|
|
||||||
model() {
|
async model() {
|
||||||
return ajax("/admin/badges.json").then((json) => {
|
let json = await ajax("/admin/badges.json");
|
||||||
this._json = json;
|
this._json = json;
|
||||||
return Badge.createFromJson(json);
|
return Badge.createFromJson(json);
|
||||||
});
|
}
|
||||||
},
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
const json = this._json;
|
const json = this._json;
|
||||||
@ -31,12 +30,11 @@ export default DiscourseRoute.extend({
|
|||||||
badgeGroupings.push(BadgeGrouping.create(badgeGroupingJson));
|
badgeGroupings.push(BadgeGrouping.create(badgeGroupingJson));
|
||||||
});
|
});
|
||||||
|
|
||||||
controller.setProperties({
|
controller.badgeGroupings = badgeGroupings;
|
||||||
badgeGroupings,
|
controller.badgeTypes = json.badge_types;
|
||||||
badgeTypes: json.badge_types,
|
controller.protectedSystemFields =
|
||||||
protectedSystemFields: json.admin_badges.protected_system_fields,
|
json.admin_badges.protected_system_fields;
|
||||||
badgeTriggers,
|
controller.badgeTriggers = badgeTriggers;
|
||||||
model,
|
controller.model = model;
|
||||||
});
|
}
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Route from "discourse/routes/discourse";
|
import Route from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default Route.extend({
|
export default class AdminBadgesAwardRoute extends Route {
|
||||||
model(params) {
|
model(params) {
|
||||||
if (params.badge_id !== "new") {
|
if (params.badge_id !== "new") {
|
||||||
return this.modelFor("adminBadges").findBy(
|
return this.modelFor("adminBadges").findBy(
|
||||||
@ -8,10 +8,10 @@ export default Route.extend({
|
|||||||
parseInt(params.badge_id, 10)
|
parseInt(params.badge_id, 10)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
setupController(controller) {
|
setupController(controller) {
|
||||||
this._super(...arguments);
|
super.setupController(...arguments);
|
||||||
controller.resetState();
|
controller.resetState();
|
||||||
},
|
}
|
||||||
});
|
}
|
@ -14,11 +14,9 @@ const badgeIntroLinks = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default Route.extend({
|
export default class AdminBadgesIndexRoute extends Route {
|
||||||
setupController(controller) {
|
setupController(controller) {
|
||||||
controller.setProperties({
|
controller.badgeIntroLinks = badgeIntroLinks;
|
||||||
badgeIntroLinks,
|
controller.badgeIntroEmoji = emojiUrlFor("woman_student:t4");
|
||||||
badgeIntroEmoji: emojiUrlFor("woman_student:t4"),
|
}
|
||||||
});
|
}
|
||||||
},
|
|
||||||
});
|
|
@ -0,0 +1,64 @@
|
|||||||
|
import Badge from "discourse/models/badge";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import Route from "@ember/routing/route";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import bootbox from "bootbox";
|
||||||
|
import { action, get } from "@ember/object";
|
||||||
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
|
||||||
|
export default class AdminBadgesShowRoute extends Route {
|
||||||
|
serialize(m) {
|
||||||
|
return { badge_id: get(m, "id") || "new" };
|
||||||
|
}
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
if (params.badge_id === "new") {
|
||||||
|
return Badge.create({
|
||||||
|
name: I18n.t("admin.badges.new_badge"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return this.modelFor("adminBadges").findBy(
|
||||||
|
"id",
|
||||||
|
parseInt(params.badge_id, 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
super.setupController(...arguments);
|
||||||
|
if (model.image_url) {
|
||||||
|
controller.showImageUploader();
|
||||||
|
} else if (model.icon) {
|
||||||
|
controller.showIconSelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
editGroupings() {
|
||||||
|
const model = this.controllerFor("admin-badges").get("badgeGroupings");
|
||||||
|
showModal("admin-edit-badge-groupings", { model, admin: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
preview(badge, explain) {
|
||||||
|
badge.set("preview_loading", true);
|
||||||
|
ajax("/admin/badges/preview.json", {
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
sql: badge.get("query"),
|
||||||
|
target_posts: !!badge.get("target_posts"),
|
||||||
|
trigger: badge.get("trigger"),
|
||||||
|
explain,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(function (model) {
|
||||||
|
badge.set("preview_loading", false);
|
||||||
|
showModal("admin-badge-preview", { model, admin: true });
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
badge.set("preview_loading", false);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(error);
|
||||||
|
bootbox.alert("Network error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -159,7 +159,7 @@
|
|||||||
<DButton @class="btn-primary" @action={{action "save"}} @type="submit" @disabled={{this.saving}} @label="admin.badges.save" />
|
<DButton @class="btn-primary" @action={{action "save"}} @type="submit" @disabled={{this.saving}} @label="admin.badges.save" />
|
||||||
<span class="saving">{{this.savingStatus}}</span>
|
<span class="saving">{{this.savingStatus}}</span>
|
||||||
{{#unless this.readOnly}}
|
{{#unless this.readOnly}}
|
||||||
<DButton @action={{action "destroy"}} @class="btn-danger" @label="admin.badges.delete" />
|
<DButton @action={{action "destroyBadge"}} @class="btn-danger" @label="admin.badges.delete" />
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
@ -353,6 +353,7 @@ export function buildResolver(baseName) {
|
|||||||
resolved =
|
resolved =
|
||||||
// Built-in
|
// Built-in
|
||||||
this.findTemplate(adminParsedName, "admin/templates/") ||
|
this.findTemplate(adminParsedName, "admin/templates/") ||
|
||||||
|
this.findTemplate(parsedName, "admin/templates/") ||
|
||||||
// Plugin
|
// Plugin
|
||||||
this.findTemplate(adminParsedName, "javascripts/admin/");
|
this.findTemplate(adminParsedName, "javascripts/admin/");
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import Component from "@ember/component";
|
import Component from "@glimmer/component";
|
||||||
import { computed } from "@ember/object";
|
|
||||||
import domFromString from "discourse-common/lib/dom-from-string";
|
import domFromString from "discourse-common/lib/dom-from-string";
|
||||||
|
|
||||||
|
// Takes @badge as argument.
|
||||||
export default class BadgeButtonComponent extends Component {
|
export default class BadgeButtonComponent extends Component {
|
||||||
tagName = "";
|
|
||||||
badge = null;
|
|
||||||
|
|
||||||
@computed("badge.description")
|
|
||||||
get title() {
|
get title() {
|
||||||
if (this.badge?.description) {
|
const description = this.args.badge?.description;
|
||||||
return domFromString(`<div>${this.badge?.description}</div>`)[0]
|
if (description) {
|
||||||
.innerText;
|
return domFromString(`<div>${description}</div>`)[0].innerText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<span class="user-badge {{this.badge.badgeTypeClassName}} {{unless this.badge.enabled "disabled"}}" title={{this.title}} data-badge-name={{this.badge.name}}>
|
<span class="user-badge {{@badge.badgeTypeClassName}} {{unless @badge.enabled "disabled"}}" title={{this.title}} data-badge-name={{@badge.name}}>
|
||||||
{{icon-or-image this.badge}}
|
{{icon-or-image @badge}}
|
||||||
<span class="badge-display-name">{{this.badge.name}}</span>
|
<span class="badge-display-name">{{@badge.name}}</span>
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</span>
|
</span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user