DEV: Convert admin emojis UI to new layout (#29615)
This PR converts the custom emoji UI in the admin pages to follow the new admin UI guidelines.
This commit is contained in:
parent
4ad83a98a1
commit
189d98f3ca
|
@ -0,0 +1,72 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { sort } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
const ALL_FILTER = "all";
|
||||
|
||||
export default class AdminEmojisIndexController extends Controller {
|
||||
@service dialog;
|
||||
|
||||
filter = null;
|
||||
sorting = null;
|
||||
|
||||
@sort("filteredEmojis.[]", "sorting") sortedEmojis;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
|
||||
this.setProperties({
|
||||
filter: ALL_FILTER,
|
||||
sorting: ["group", "name"],
|
||||
});
|
||||
}
|
||||
|
||||
@computed("model.[]", "filter")
|
||||
get filteredEmojis() {
|
||||
if (!this.filter || this.filter === ALL_FILTER) {
|
||||
return this.model;
|
||||
} else {
|
||||
return this.model.filterBy("group", this.filter);
|
||||
}
|
||||
}
|
||||
|
||||
@computed("model.[]")
|
||||
get emojiGroups() {
|
||||
return this.model.mapBy("group").uniq();
|
||||
}
|
||||
|
||||
@computed("emojiGroups.[]")
|
||||
get sortingGroups() {
|
||||
return [ALL_FILTER].concat(this.emojiGroups);
|
||||
}
|
||||
|
||||
@action
|
||||
filterGroups(value) {
|
||||
this.set("filter", value);
|
||||
}
|
||||
|
||||
@action
|
||||
destroyEmoji(emoji) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.emoji.delete_confirm", {
|
||||
name: emoji.get("name"),
|
||||
}),
|
||||
didConfirm: () => this.#destroyEmoji(emoji),
|
||||
});
|
||||
}
|
||||
|
||||
async #destroyEmoji(emoji) {
|
||||
try {
|
||||
await ajax("/admin/customize/emojis/" + emoji.get("name"), {
|
||||
type: "DELETE",
|
||||
});
|
||||
this.model.removeObject(emoji);
|
||||
} catch (err) {
|
||||
popupAjaxError(err);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import Controller from "@ember/controller";
|
||||
import EmberObject, { action, computed } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
|
||||
const ALL_FILTER = "all";
|
||||
|
||||
export default class AdminEmojisNewController extends Controller {
|
||||
@service router;
|
||||
@service currentUser;
|
||||
|
||||
@computed("model")
|
||||
get emojiGroups() {
|
||||
return this.model.mapBy("group").uniq();
|
||||
}
|
||||
|
||||
@computed("emojiGroups.[]")
|
||||
get sortingGroups() {
|
||||
return [ALL_FILTER].concat(this.emojiGroups);
|
||||
}
|
||||
|
||||
@action
|
||||
emojiUploaded(emoji, group) {
|
||||
emoji.url += "?t=" + new Date().getTime();
|
||||
emoji.group = group;
|
||||
emoji.created_by = this.currentUser.username;
|
||||
this.model.pushObject(EmberObject.create(emoji));
|
||||
this.router.transitionTo("adminEmojis.index");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
|
||||
export default class AdminEmojisSettingsController extends Controller {
|
||||
filter = "";
|
||||
|
||||
@action
|
||||
filterChanged(filterData) {
|
||||
this.set("filter", filterData.filter);
|
||||
}
|
||||
}
|
|
@ -1,86 +1,3 @@
|
|||
import Controller from "@ember/controller";
|
||||
import EmberObject, { action, computed } from "@ember/object";
|
||||
import { sort } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
const ALL_FILTER = "all";
|
||||
|
||||
export default class AdminEmojisController extends Controller {
|
||||
@service dialog;
|
||||
|
||||
filter = null;
|
||||
sorting = null;
|
||||
|
||||
@sort("filteredEmojis.[]", "sorting") sortedEmojis;
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
|
||||
this.setProperties({
|
||||
filter: ALL_FILTER,
|
||||
sorting: ["group", "name"],
|
||||
});
|
||||
}
|
||||
|
||||
@computed("model")
|
||||
get emojiGroups() {
|
||||
return this.model.mapBy("group").uniq();
|
||||
}
|
||||
|
||||
@computed("emojiGroups.[]")
|
||||
get sortingGroups() {
|
||||
return [ALL_FILTER].concat(this.emojiGroups);
|
||||
}
|
||||
|
||||
@computed("model.[]", "filter")
|
||||
get filteredEmojis() {
|
||||
if (!this.filter || this.filter === ALL_FILTER) {
|
||||
return this.model;
|
||||
} else {
|
||||
return this.model.filterBy("group", this.filter);
|
||||
}
|
||||
}
|
||||
|
||||
_highlightEmojiList() {
|
||||
const customEmojiListEl = document.querySelector("#custom_emoji");
|
||||
if (
|
||||
customEmojiListEl &&
|
||||
!customEmojiListEl.classList.contains("highlighted")
|
||||
) {
|
||||
customEmojiListEl.classList.add("highlighted");
|
||||
customEmojiListEl.addEventListener("animationend", () => {
|
||||
customEmojiListEl.classList.remove("highlighted");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
filterGroups(value) {
|
||||
this.set("filter", value);
|
||||
}
|
||||
|
||||
@action
|
||||
emojiUploaded(emoji, group) {
|
||||
emoji.url += "?t=" + new Date().getTime();
|
||||
emoji.group = group;
|
||||
this.model.pushObject(EmberObject.create(emoji));
|
||||
this._highlightEmojiList();
|
||||
}
|
||||
|
||||
@action
|
||||
destroyEmoji(emoji) {
|
||||
this.dialog.yesNoConfirm({
|
||||
message: I18n.t("admin.emoji.delete_confirm", {
|
||||
name: emoji.get("name"),
|
||||
}),
|
||||
didConfirm: () => {
|
||||
return ajax("/admin/customize/emojis/" + emoji.get("name"), {
|
||||
type: "DELETE",
|
||||
}).then(() => {
|
||||
this.model.removeObject(emoji);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
export default class AdminEmojisController extends Controller {}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import I18n from "discourse-i18n";
|
||||
import SiteSetting from "admin/models/site-setting";
|
||||
|
||||
export default class AdminEmojisSettingsRoute extends DiscourseRoute {
|
||||
queryParams = {
|
||||
filter: { replace: true },
|
||||
};
|
||||
|
||||
titleToken() {
|
||||
return I18n.t("settings");
|
||||
}
|
||||
|
||||
async model() {
|
||||
return {
|
||||
settings: await SiteSetting.findAll(),
|
||||
initialFilter: "emoji",
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class AdminEmojisRoute extends DiscourseRoute {
|
||||
titleToken() {
|
||||
return I18n.t("admin.emoji.title");
|
||||
}
|
||||
|
||||
async model() {
|
||||
const emojis = await ajax("/admin/customize/emojis.json");
|
||||
return emojis.map((emoji) => EmberObject.create(emoji));
|
||||
|
|
|
@ -72,7 +72,15 @@ export default function () {
|
|||
path: "/user_fields",
|
||||
resetNamespace: true,
|
||||
});
|
||||
this.route("adminEmojis", { path: "/emojis", resetNamespace: true });
|
||||
this.route(
|
||||
"adminEmojis",
|
||||
{ path: "/emojis", resetNamespace: true },
|
||||
function () {
|
||||
this.route("new");
|
||||
this.route("index", { path: "/" });
|
||||
this.route("settings");
|
||||
}
|
||||
);
|
||||
this.route("adminPermalinks", {
|
||||
path: "/permalinks",
|
||||
resetNamespace: true,
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<div class="form-horizontal">
|
||||
<div class="inline-form">
|
||||
<ComboBox
|
||||
@value={{this.filter}}
|
||||
@content={{this.sortingGroups}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@onChange={{action "filterGroups"}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.sortedEmojis}}
|
||||
<table id="custom_emoji" class="d-admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n "admin.emoji.image"}}</th>
|
||||
<th>{{i18n "admin.emoji.name"}}</th>
|
||||
<th>{{i18n "admin.emoji.group"}}</th>
|
||||
<th colspan="3">{{i18n "admin.emoji.created_by"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.sortedEmojis as |emoji|}}
|
||||
<tr class="d-admin-row__content">
|
||||
<td class="d-admin-row__overview">
|
||||
<img
|
||||
class="emoji emoji-custom"
|
||||
src={{emoji.url}}
|
||||
title={{emoji.name}}
|
||||
alt={{i18n "admin.emoji.alt"}}
|
||||
/>
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.name"}}
|
||||
</div>
|
||||
:{{emoji.name}}:
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.group"}}
|
||||
</div>
|
||||
{{emoji.group}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.created_by"}}
|
||||
</div>
|
||||
{{emoji.created_by}}
|
||||
</td>
|
||||
<td class="d-admin-row__controls action">
|
||||
<DButton
|
||||
@action={{fn this.destroyEmoji emoji}}
|
||||
@icon="trash-can"
|
||||
class="btn-small"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/if}}
|
|
@ -0,0 +1,4 @@
|
|||
<EmojiUploader
|
||||
@emojiGroups={{this.emojiGroups}}
|
||||
@done={{action "emojiUploaded"}}
|
||||
/>
|
|
@ -0,0 +1,9 @@
|
|||
<DBreadcrumbsItem @path="/admin/emojis/settings" @label={{i18n "settings"}} />
|
||||
|
||||
<div class="content-body admin-config-area__settings admin-detail pull-left">
|
||||
<AdminFilteredSiteSettings
|
||||
@initialFilter={{@model.initialFilter}}
|
||||
@settings={{@model.settings}}
|
||||
@onFilterChanged={{this.filterChanged}}
|
||||
/>
|
||||
</div>
|
|
@ -1,86 +1,32 @@
|
|||
<div class="admin-emojis">
|
||||
<div class="admin-emojis__header">
|
||||
<h1>{{i18n "admin.emoji.title"}}</h1>
|
||||
<LinkTo
|
||||
@route="adminSiteSettingsCategory"
|
||||
@model="all_results"
|
||||
@query={{hash filter="emoji"}}
|
||||
>
|
||||
{{i18n "admin.emoji.settings"}}
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
<p class="desc">{{i18n "admin.emoji.help"}}</p>
|
||||
|
||||
<EmojiUploader
|
||||
@emojiGroups={{this.emojiGroups}}
|
||||
@done={{action "emojiUploaded"}}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="form-horizontal">
|
||||
<div class="inline-form">
|
||||
<label class="label">Show</label>
|
||||
<ComboBox
|
||||
@value={{this.filter}}
|
||||
@content={{this.sortingGroups}}
|
||||
@nameProperty={{null}}
|
||||
@valueProperty={{null}}
|
||||
@onChange={{action "filterGroups"}}
|
||||
<div class="admin-emojis admin-config-page">
|
||||
<AdminPageHeader
|
||||
@titleLabel="admin.emoji.title"
|
||||
@descriptionLabel="admin.emoji.description"
|
||||
>
|
||||
<:breadcrumbs>
|
||||
<DBreadcrumbsItem
|
||||
@path="/admin/customize/emojis"
|
||||
@label={{i18n "admin.emoji.title"}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</:breadcrumbs>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary @route="adminEmojis.new" @label="admin.emoji.new" />
|
||||
</:actions>
|
||||
<:tabs>
|
||||
<NavItem
|
||||
@route="adminEmojis.settings"
|
||||
@label="settings"
|
||||
class="admin-emojis-tabs__settings"
|
||||
/>
|
||||
<NavItem
|
||||
@route="adminEmojis.index"
|
||||
@label="admin.emoji.title"
|
||||
class="admin-emojis-tabs__emoji"
|
||||
/>
|
||||
</:tabs>
|
||||
</AdminPageHeader>
|
||||
|
||||
{{#if this.sortedEmojis}}
|
||||
<table id="custom_emoji" class="d-admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{i18n "admin.emoji.image"}}</th>
|
||||
<th>{{i18n "admin.emoji.name"}}</th>
|
||||
<th>{{i18n "admin.emoji.group"}}</th>
|
||||
<th colspan="3">{{i18n "admin.emoji.created_by"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.sortedEmojis as |e|}}
|
||||
<tr class="d-admin-row__content">
|
||||
<td class="d-admin-row__overview">
|
||||
<img
|
||||
class="emoji emoji-custom"
|
||||
src={{e.url}}
|
||||
title={{e.name}}
|
||||
alt={{i18n "admin.emoji.alt"}}
|
||||
/>
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.name"}}
|
||||
</div>
|
||||
:{{e.name}}:
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.group"}}
|
||||
</div>
|
||||
{{e.group}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
<div class="d-admin-row__mobile-label">
|
||||
{{i18n "admin.emoji.created_by"}}
|
||||
</div>
|
||||
{{e.created_by}}
|
||||
</td>
|
||||
<td class="d-admin-row__controls action">
|
||||
<DButton
|
||||
@action={{fn this.destroyEmoji e}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/if}}
|
||||
<div class="admin-container admin-config-page__main-area">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
|
@ -7204,7 +7204,8 @@ en:
|
|||
|
||||
emoji:
|
||||
title: "Emoji"
|
||||
help: "Add new emoji that will be available to everyone. Drag and drop multiple files at once without entering a name to create emojis using their file names. The selected group will be used for all files that are added at the same time. You can also click 'Add New Emoji' to open the file picker."
|
||||
description: "Add new emoji that will be available to everyone. Drag and drop multiple files at once without entering a name to create emojis using their file names. The selected group will be used for all files that are added at the same time. You can also click 'Add New Emoji' to open the file picker."
|
||||
new: "Add"
|
||||
add: "Add New Emoji"
|
||||
choose_files: "Choose Files"
|
||||
uploading: "Uploading…"
|
||||
|
|
|
@ -246,6 +246,8 @@ Discourse::Application.routes.draw do
|
|||
only: %i[index create update destroy],
|
||||
constraints: AdminConstraint.new
|
||||
resources :emojis, only: %i[index create destroy], constraints: AdminConstraint.new
|
||||
get "emojis/new" => "emojis#index"
|
||||
get "emojis/settings" => "emojis#index"
|
||||
resources :form_templates, constraints: AdminConstraint.new, path: "/form-templates" do
|
||||
collection { get "preview" => "form_templates#preview" }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:custom_emoji) do
|
||||
upload { Fabricate(:image_upload) }
|
||||
|
||||
name { "joffrey_facepalm" }
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
#frozen_string_literal: true
|
||||
|
||||
describe "Admin Customize Emoji Page", type: :system do
|
||||
fab!(:current_user) { Fabricate(:admin) }
|
||||
|
||||
let(:emojis_page) { PageObjects::Pages::AdminEmojis.new }
|
||||
let(:dialog) { PageObjects::Components::Dialog.new }
|
||||
let(:settings_page) { PageObjects::Pages::AdminSiteSettings.new }
|
||||
|
||||
before do
|
||||
Fabricate(:custom_emoji)
|
||||
|
||||
sign_in(current_user)
|
||||
end
|
||||
|
||||
it "shows a list of custom emojis" do
|
||||
emojis_page.visit_page
|
||||
expect(emojis_page).to have_emoji_listed("joffrey_facepalm")
|
||||
end
|
||||
|
||||
it "can delete a custom emoji" do
|
||||
emojis_page.visit_page
|
||||
emojis_page.delete_emoji("joffrey_facepalm")
|
||||
dialog.click_yes
|
||||
expect(emojis_page).to have_no_emoji_listed("joffrey_facepalm")
|
||||
end
|
||||
|
||||
it "can see emoji site settings" do
|
||||
emojis_page.visit_page
|
||||
emojis_page.click_tab("settings")
|
||||
expect(settings_page).to have_setting("enable_emoji")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class AdminEmojis < PageObjects::Pages::Base
|
||||
def visit_page
|
||||
page.visit "/admin/customize/emojis"
|
||||
self
|
||||
end
|
||||
|
||||
def click_tab(tab_name)
|
||||
case tab_name
|
||||
when "settings"
|
||||
find(".admin-emojis-tabs__settings").click
|
||||
when "index"
|
||||
find(".admin-emojis-tabs__emoji").click
|
||||
end
|
||||
end
|
||||
|
||||
def has_emoji_listed?(name)
|
||||
page.has_css?(emoji_table_selector, text: name)
|
||||
end
|
||||
|
||||
def has_no_emoji_listed?(name)
|
||||
page.has_no_css?(emoji_table_selector, text: name)
|
||||
end
|
||||
|
||||
def delete_emoji(name)
|
||||
find(".d-admin-row__content", text: name).find(delete_button_selector).click
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def emoji_table_selector
|
||||
"#custom_emoji"
|
||||
end
|
||||
|
||||
def delete_button_selector
|
||||
".d-icon-trash-can"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue