DEV: Convert user-status-picker to glimmer/gjs/dbutton/input (#28344)

This commit is contained in:
Jarek Radosz 2024-08-13 18:57:57 +02:00 committed by GitHub
parent 82741eb0a6
commit c96dce2934
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 157 additions and 162 deletions

View File

@ -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>
}

View File

@ -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"
/>

View File

@ -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);
}
}

View File

@ -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");
});
});

View File

@ -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);
});
});