mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 09:26:54 -06:00
UX: Easily toggle badges in admin badge list (#20225)
This commit is contained in:
parent
58123e8089
commit
6338287e89
@ -1,7 +1,6 @@
|
|||||||
import Controller, { inject as controller } from "@ember/controller";
|
import Controller, { inject as controller } from "@ember/controller";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
|
|
||||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { next } from "@ember/runloop";
|
import { next } from "@ember/runloop";
|
||||||
@ -25,6 +24,14 @@ export default class AdminBadgesShowController extends Controller.extend(
|
|||||||
@tracked savingStatus = "";
|
@tracked savingStatus = "";
|
||||||
@tracked selectedGraphicType = null;
|
@tracked selectedGraphicType = null;
|
||||||
|
|
||||||
|
get badgeEnabledLabel() {
|
||||||
|
if (this.buffered.get("enabled")) {
|
||||||
|
return "admin.badges.enabled";
|
||||||
|
} else {
|
||||||
|
return "admin.badges.disabled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get badgeTypes() {
|
get badgeTypes() {
|
||||||
return this.adminBadges.badgeTypes;
|
return this.adminBadges.badgeTypes;
|
||||||
}
|
}
|
||||||
@ -238,4 +245,11 @@ export default class AdminBadgesShowController extends Controller.extend(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleBadge() {
|
||||||
|
this.model
|
||||||
|
.save({ enabled: !this.buffered.get("enabled") })
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
<DSection @class="current-badge content-body">
|
<DSection @class="current-badge content-body">
|
||||||
|
<div class="control-group current-badge__toggle-badge">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{this.buffered.enabled}}
|
||||||
|
@label={{this.badgeEnabledLabel}}
|
||||||
|
{{on "click" this.toggleBadge}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="name">{{i18n "admin.badges.name"}}</label>
|
<label for="name">{{i18n "admin.badges.name"}}</label>
|
||||||
@ -253,13 +261,6 @@
|
|||||||
{{i18n "admin.badges.show_posts"}}
|
{{i18n "admin.badges.show_posts"}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<Input @type="checkbox" @checked={{this.buffered.enabled}} />
|
|
||||||
{{i18n "admin.badges.enabled"}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
<div class="d-toggle-switch">
|
||||||
|
<label class="d-toggle-switch--label">
|
||||||
|
{{! template-lint-disable no-unnecessary-concat }}
|
||||||
|
<button
|
||||||
|
class="d-toggle-switch__checkbox"
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
aria-checked="{{@state}}"
|
||||||
|
...attributes
|
||||||
|
></button>
|
||||||
|
<span class="d-toggle-switch__checkbox-slider">
|
||||||
|
{{#if @state}}
|
||||||
|
{{d-icon "check"}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<span class="d-toggle-switch__checkbox-label">
|
||||||
|
{{this.computedLabel}}
|
||||||
|
</span>
|
||||||
|
</div>
|
@ -0,0 +1,15 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class DiscourseToggleSwitch extends Component {
|
||||||
|
@tracked iconEnabled = true;
|
||||||
|
@tracked showIcon = this.iconEnabled && this.icon;
|
||||||
|
|
||||||
|
get computedLabel() {
|
||||||
|
if (this.args.label) {
|
||||||
|
return I18n.t(this.args.label);
|
||||||
|
}
|
||||||
|
return this.args.translatedLabel;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import { module, test } from "qunit";
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
|
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
module("Integration | Component | d-toggle-switch", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("it renders a toggle button in a disabled state", async function (assert) {
|
||||||
|
this.set("state", false);
|
||||||
|
|
||||||
|
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||||
|
|
||||||
|
assert.ok(exists(".d-toggle-switch"), "it renders a toggle switch");
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".d-toggle-switch__checkbox").getAttribute("aria-checked"),
|
||||||
|
"false"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it renders a toggle button in a enabled state", async function (assert) {
|
||||||
|
this.set("state", true);
|
||||||
|
|
||||||
|
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||||
|
|
||||||
|
assert.ok(exists(".d-toggle-switch"), "it renders a toggle switch");
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".d-toggle-switch__checkbox").getAttribute("aria-checked"),
|
||||||
|
"true"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it renders a checkmark icon when enabled", async function (assert) {
|
||||||
|
this.set("state", true);
|
||||||
|
|
||||||
|
await render(hbs`<DToggleSwitch @state={{this.state}}/>`);
|
||||||
|
assert.ok(exists(".d-toggle-switch__checkbox-slider .d-icon-check"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it renders a label for the button", async function (assert) {
|
||||||
|
I18n.translations[I18n.locale].js.test = { fooLabel: "foo" };
|
||||||
|
this.set("state", true);
|
||||||
|
await render(
|
||||||
|
hbs`<DToggleSwitch @state={{this.state}}/ @label={{this.label}} @translatedLabel={{this.translatedLabel}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.set("label", "test.fooLabel");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".d-toggle-switch__checkbox-label").innerText,
|
||||||
|
I18n.t("test.fooLabel")
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
label: null,
|
||||||
|
translatedLabel: "bar",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
query(".d-toggle-switch__checkbox-label").innerText,
|
||||||
|
"bar"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -7,6 +7,7 @@
|
|||||||
@import "conditional-loading-section";
|
@import "conditional-loading-section";
|
||||||
@import "convert-to-public-topic-modal";
|
@import "convert-to-public-topic-modal";
|
||||||
@import "d-tooltip";
|
@import "d-tooltip";
|
||||||
|
@import "d-toggle-switch";
|
||||||
@import "date-input";
|
@import "date-input";
|
||||||
@import "date-picker";
|
@import "date-picker";
|
||||||
@import "date-time-input-range";
|
@import "date-time-input-range";
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
.d-toggle-switch {
|
||||||
|
--toggle-switch-width: 45px;
|
||||||
|
--toggle-switch-height: 24px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
.d-toggle-switch__checkbox-slider {
|
||||||
|
outline: 2px solid var(--tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.d-toggle-switch__checkbox-slider {
|
||||||
|
background-color: var(--primary-high);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-toggle-switch__checkbox[aria-checked="true"]
|
||||||
|
+ .d-toggle-switch__checkbox-slider {
|
||||||
|
background-color: var(--tertiary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox {
|
||||||
|
position: absolute;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox[aria-checked="true"] + .d-toggle-switch__checkbox-slider {
|
||||||
|
background-color: var(--tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox[aria-checked="true"] + .d-toggle-switch__checkbox-slider::before {
|
||||||
|
left: calc(var(--toggle-switch-width) - 22px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-slider {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--primary-low-mid);
|
||||||
|
border-radius: 16px;
|
||||||
|
width: var(--toggle-switch-width);
|
||||||
|
height: var(--toggle-switch-height);
|
||||||
|
margin-right: 0.5em;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: background 0.25s;
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
color: var(--secondary);
|
||||||
|
left: 7px;
|
||||||
|
top: 7px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-slider::before,
|
||||||
|
&__checkbox-slider::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__checkbox-slider::before {
|
||||||
|
background: var(--secondary);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: calc(var(--toggle-switch-width) / 2.5);
|
||||||
|
height: calc(var(--toggle-switch-width) / 2.5);
|
||||||
|
top: 3.5px;
|
||||||
|
left: 4px;
|
||||||
|
transition: left 0.25s;
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ class Admin::BadgesController < Admin::AdminController
|
|||||||
.includes(:badge_grouping)
|
.includes(:badge_grouping)
|
||||||
.includes(:badge_type, :image_upload)
|
.includes(:badge_type, :image_upload)
|
||||||
.references(:badge_grouping)
|
.references(:badge_grouping)
|
||||||
.order("badge_groupings.position, badge_type_id, badges.name")
|
.order("enabled DESC", "badge_groupings.position, badge_type_id, badges.name")
|
||||||
.to_a,
|
.to_a,
|
||||||
protected_system_fields: Badge.protected_system_fields,
|
protected_system_fields: Badge.protected_system_fields,
|
||||||
triggers: Badge.trigger_hash,
|
triggers: Badge.trigger_hash,
|
||||||
|
@ -6013,7 +6013,8 @@ en:
|
|||||||
allow_title: Allow badge to be used as a title
|
allow_title: Allow badge to be used as a title
|
||||||
multiple_grant: Can be granted multiple times
|
multiple_grant: Can be granted multiple times
|
||||||
listable: Show badge on the public badges page
|
listable: Show badge on the public badges page
|
||||||
enabled: Enable badge
|
enabled: enabled
|
||||||
|
disabled: disabled
|
||||||
icon: Icon
|
icon: Icon
|
||||||
image: Image
|
image: Image
|
||||||
graphic: Graphic
|
graphic: Graphic
|
||||||
|
@ -217,6 +217,8 @@ export function createData(store) {
|
|||||||
{ disabled: true, text: "disabled" },
|
{ disabled: true, text: "disabled" },
|
||||||
],
|
],
|
||||||
|
|
||||||
|
toggleSwitchState: true,
|
||||||
|
|
||||||
navItems: ["latest", "categories", "top"].map((name) => {
|
navItems: ["latest", "categories", "top"].map((name) => {
|
||||||
let item = NavItem.fromText(name);
|
let item = NavItem.fromText(name);
|
||||||
|
|
||||||
|
@ -152,4 +152,14 @@
|
|||||||
@translatedLabel={{bs.text}}
|
@translatedLabel={{bs.text}}
|
||||||
/>
|
/>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
</StyleguideExample>
|
||||||
|
|
||||||
|
<StyleguideExample @title="DToggleSwitch">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{this.dummy.toggleSwitchState}}
|
||||||
|
{{on
|
||||||
|
"click"
|
||||||
|
(fn (mut this.dummy.toggleSwitchState) (not this.dummy.toggleSwitchState))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</StyleguideExample>
|
</StyleguideExample>
|
Loading…
Reference in New Issue
Block a user