DEV: Convert user-status-picker to glimmer/gjs/dbutton/input (#28344)
This commit is contained in:
parent
82741eb0a6
commit
c96dce2934
|
@ -0,0 +1,99 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import DButton from "discourse/components/d-button";
|
||||||
|
import EmojiPicker from "discourse/components/emoji-picker";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
|
import autoFocus from "discourse/modifiers/auto-focus";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
|
||||||
|
export default class UserStatusPicker extends Component {
|
||||||
|
@tracked isFocused = false;
|
||||||
|
@tracked emojiPickerIsActive = false;
|
||||||
|
|
||||||
|
get emojiHtml() {
|
||||||
|
return emojiUnescape(escapeExpression(`:${this.args.status.emoji}:`));
|
||||||
|
}
|
||||||
|
|
||||||
|
focusEmojiButton() {
|
||||||
|
document.querySelector(".user-status-picker .btn-emoji")?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
blur() {
|
||||||
|
this.isFocused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
emojiSelected(emoji) {
|
||||||
|
this.args.status.emoji = emoji;
|
||||||
|
this.emojiPickerIsActive = false;
|
||||||
|
|
||||||
|
scheduleOnce("afterRender", this, this.focusEmojiButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
focus() {
|
||||||
|
this.isFocused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onEmojiPickerOutsideClick() {
|
||||||
|
this.emojiPickerIsActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
updateDescription(event) {
|
||||||
|
this.args.status.description = event.target.value;
|
||||||
|
this.args.status.emoji ||= "speech_balloon";
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleEmojiPicker() {
|
||||||
|
this.emojiPickerIsActive = !this.emojiPickerIsActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="user-status-picker-wrap">
|
||||||
|
<div
|
||||||
|
class={{concatClass
|
||||||
|
"emoji-picker-anchor user-status-picker"
|
||||||
|
(if this.isFocused "focused")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DButton
|
||||||
|
{{on "focus" this.focus}}
|
||||||
|
{{on "blur" this.blur}}
|
||||||
|
@action={{this.toggleEmojiPicker}}
|
||||||
|
@icon={{unless @status.emoji "discourse-emojis"}}
|
||||||
|
@translatedLabel={{if @status.emoji (htmlSafe this.emojiHtml)}}
|
||||||
|
class="btn-emoji btn-transparent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
{{on "input" this.updateDescription}}
|
||||||
|
{{on "focus" this.focus}}
|
||||||
|
{{on "blur" this.blur}}
|
||||||
|
{{autoFocus}}
|
||||||
|
value={{@status.description}}
|
||||||
|
type="text"
|
||||||
|
placeholder={{i18n "user_status.what_are_you_doing"}}
|
||||||
|
maxlength="100"
|
||||||
|
class="user-status-description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EmojiPicker
|
||||||
|
@isActive={{this.emojiPickerIsActive}}
|
||||||
|
@emojiSelected={{this.emojiSelected}}
|
||||||
|
@onEmojiPickerClose={{this.onEmojiPickerOutsideClick}}
|
||||||
|
@placement="bottom"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -1,35 +0,0 @@
|
||||||
<div class="user-status-picker-wrap">
|
|
||||||
<div
|
|
||||||
class="emoji-picker-anchor user-status-picker
|
|
||||||
{{if this.isFocused 'focused'}}"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn-emoji btn-transparent"
|
|
||||||
onclick={{this.toggleEmojiPicker}}
|
|
||||||
{{on "focus" this.focus}}
|
|
||||||
{{on "blur" this.blur}}
|
|
||||||
>
|
|
||||||
{{#if @status.emoji}}
|
|
||||||
{{html-safe this.emojiHtml}}
|
|
||||||
{{else}}
|
|
||||||
{{d-icon "discourse-emojis"}}
|
|
||||||
{{/if}}
|
|
||||||
</button>
|
|
||||||
<Input
|
|
||||||
class="user-status-description"
|
|
||||||
@value={{@status.description}}
|
|
||||||
maxlength="100"
|
|
||||||
placeholder={{i18n "user_status.what_are_you_doing"}}
|
|
||||||
{{on "input" this.setDefaultEmoji}}
|
|
||||||
{{on "focus" this.focus}}
|
|
||||||
{{on "blur" this.blur}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<EmojiPicker
|
|
||||||
@isActive={{this.emojiPickerIsActive}}
|
|
||||||
@emojiSelected={{this.emojiSelected}}
|
|
||||||
@onEmojiPickerClose={{this.onEmojiPickerOutsideClick}}
|
|
||||||
@placement="bottom"
|
|
||||||
/>
|
|
|
@ -1,67 +0,0 @@
|
||||||
import Component from "@ember/component";
|
|
||||||
import { action, computed } from "@ember/object";
|
|
||||||
import { scheduleOnce } from "@ember/runloop";
|
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
|
||||||
|
|
||||||
export default class UserStatusPicker extends Component {
|
|
||||||
tagName = "";
|
|
||||||
isFocused = false;
|
|
||||||
emojiPickerIsActive = false;
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
super.didInsertElement(...arguments);
|
|
||||||
|
|
||||||
if (!this.status) {
|
|
||||||
this.set("status", {});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector(".user-status-description")?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed("status.emoji")
|
|
||||||
get emojiHtml() {
|
|
||||||
const emoji = escapeExpression(`:${this.status.emoji}:`);
|
|
||||||
return emojiUnescape(emoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
focusEmojiButton() {
|
|
||||||
document.querySelector(".btn-emoji")?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
blur() {
|
|
||||||
this.set("isFocused", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
emojiSelected(emoji) {
|
|
||||||
this.set("status.emoji", emoji);
|
|
||||||
this.set("emojiPickerIsActive", false);
|
|
||||||
|
|
||||||
scheduleOnce("afterRender", this, this.focusEmojiButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
focus() {
|
|
||||||
this.set("isFocused", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
onEmojiPickerOutsideClick() {
|
|
||||||
this.set("emojiPickerIsActive", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
setDefaultEmoji() {
|
|
||||||
if (!this.status.emoji) {
|
|
||||||
this.set("status.emoji", "speech_balloon");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
toggleEmojiPicker(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
this.set("emojiPickerIsActive", !this.emojiPickerIsActive);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { click, fillIn, render } from "@ember/test-helpers";
|
||||||
|
import { TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import UserStatusPicker from "discourse/components/user-status-picker";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
|
module("Integration | Component | user-status-picker", function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("it renders current status", async function (assert) {
|
||||||
|
const status = new TrackedObject({
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(<template><UserStatusPicker @status={{status}} /></template>);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".emoji")
|
||||||
|
.hasAttribute("alt", status.emoji, "the status emoji is shown");
|
||||||
|
assert
|
||||||
|
.dom(".user-status-description")
|
||||||
|
.hasValue(status.description, "the status description is shown");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it focuses the input on insert", async function (assert) {
|
||||||
|
const status = new TrackedObject({});
|
||||||
|
await render(<template><UserStatusPicker @status={{status}} /></template>);
|
||||||
|
|
||||||
|
assert.dom(".user-status-description").isFocused();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it picks emoji", async function (assert) {
|
||||||
|
const status = new TrackedObject({
|
||||||
|
emoji: "tooth",
|
||||||
|
description: "off to dentist",
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(<template><UserStatusPicker @status={{status}} /></template>);
|
||||||
|
|
||||||
|
await click(".btn-emoji");
|
||||||
|
await fillIn(".emoji-picker-content .filter", "mega");
|
||||||
|
await click(".results .emoji");
|
||||||
|
|
||||||
|
assert.dom(".emoji").hasAttribute("alt", "mega");
|
||||||
|
assert.strictEqual(status.emoji, "mega");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it sets default emoji when user starts typing a description", async function (assert) {
|
||||||
|
const status = new TrackedObject({});
|
||||||
|
|
||||||
|
await render(<template><UserStatusPicker @status={{status}} /></template>);
|
||||||
|
|
||||||
|
await fillIn(".user-status-description", "s");
|
||||||
|
assert.dom(".emoji").hasAttribute("alt", "speech_balloon");
|
||||||
|
assert.strictEqual(status.emoji, "speech_balloon");
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,60 +0,0 @@
|
||||||
import { click, fillIn, render } from "@ember/test-helpers";
|
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
|
||||||
import { module, test } from "qunit";
|
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
|
||||||
import { query } from "discourse/tests/helpers/qunit-helpers";
|
|
||||||
|
|
||||||
module("Integration | Component | user-status-picker", function (hooks) {
|
|
||||||
setupRenderingTest(hooks);
|
|
||||||
|
|
||||||
test("it renders current status", async function (assert) {
|
|
||||||
const status = {
|
|
||||||
emoji: "tooth",
|
|
||||||
description: "off to dentist",
|
|
||||||
};
|
|
||||||
this.set("status", status);
|
|
||||||
await render(hbs`<UserStatusPicker @status={{this.status}} />`);
|
|
||||||
assert.equal(
|
|
||||||
query(".emoji").alt,
|
|
||||||
status.emoji,
|
|
||||||
"the status emoji is shown"
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
query(".user-status-description").value,
|
|
||||||
status.description,
|
|
||||||
"the status description is shown"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("it focuses the input on insert", async function (assert) {
|
|
||||||
await render(hbs`<UserStatusPicker />`);
|
|
||||||
|
|
||||||
assert.dom(".user-status-description").isFocused();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("it picks emoji", async function (assert) {
|
|
||||||
const status = {
|
|
||||||
emoji: "tooth",
|
|
||||||
description: "off to dentist",
|
|
||||||
};
|
|
||||||
this.set("status", status);
|
|
||||||
await render(hbs`<UserStatusPicker @status={{this.status}} />`);
|
|
||||||
|
|
||||||
const newEmoji = "mega";
|
|
||||||
await click(".btn-emoji");
|
|
||||||
await fillIn(".emoji-picker-content .filter", newEmoji);
|
|
||||||
await click(".results .emoji");
|
|
||||||
|
|
||||||
assert.equal(query(".emoji").alt, newEmoji);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("it sets default emoji when user starts typing a description", async function (assert) {
|
|
||||||
const defaultEmoji = "speech_balloon";
|
|
||||||
|
|
||||||
this.set("status", null);
|
|
||||||
await render(hbs`<UserStatusPicker @status={{this.status}} />`);
|
|
||||||
await fillIn(".user-status-description", "s");
|
|
||||||
|
|
||||||
assert.equal(query(".emoji").alt, defaultEmoji);
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue