UX: reworks channel index (drawer and mobile) (#18892)

- Multiple style improvements
- adds last sent message date to the view

Co-authored-by: chapoi <charlie@discourse.org>
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit is contained in:
Keegan George 2022-11-28 09:38:05 -08:00 committed by GitHub
parent 07a9163ea8
commit 7a8e018965
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 286 additions and 142 deletions

View File

@ -0,0 +1,16 @@
import Component from "@glimmer/component";
export default class ChatChannelMetadata extends Component {
unreadIndicator = false;
get lastMessageFormatedDate() {
return moment(this.args.channel.last_message_sent_at).calendar(null, {
sameDay: "LT",
nextDay: "[Tomorrow]",
nextWeek: "dddd",
lastDay: "[Yesterday]",
lastWeek: "dddd",
sameElse: "l",
});
}
}

View File

@ -47,6 +47,7 @@ export default Component.extend({
@discourseComputed("active", "channel.{id,muted}", "channel.focused")
rowClassNames(active, channel, focused) {
const classes = ["chat-channel-row", `chat-channel-${channel.id}`];
if (active) {
classes.push("active");
}
@ -56,6 +57,16 @@ export default Component.extend({
if (channel.current_user_membership.muted) {
classes.push("muted");
}
if (this.options?.leaveButton) {
classes.push("can-leave");
}
const channelUnreadCount =
this.currentUser.chat_channel_tracking_state?.[channel.id]?.unread_count;
if (channelUnreadCount > 0) {
classes.push("has-unread");
}
return classes.join(" ");
},

View File

@ -6,7 +6,6 @@ import { gt, reads } from "@ember/object/computed";
export default class ChatChannelTitle extends Component {
tagName = "";
channel = null;
unreadIndicator = false;
@reads("channel.chatable.users.[]") users;
@gt("users.length", 1) multiDm;
@ -20,4 +19,15 @@ export default class ChatChannelTitle extends Component {
get channelColorStyle() {
return htmlSafe(`color: #${this.channel.chatable.color}`);
}
@computed(
"channel.chatable.users.length",
"channel.chatable.users.@each.status"
)
get showUserStatus() {
return !!(
this.channel.chatable.users.length === 1 &&
this.channel.chatable.users[0].status
);
}
}

View File

@ -0,0 +1,9 @@
<div class="chat-channel-metadata">
<div class="chat-channel-metadata__date">
{{this.lastMessageFormatedDate}}
</div>
{{#if @unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{@channel}} />
{{/if}}
</div>

View File

@ -1,9 +1,6 @@
<LinkTo @route="chat.channel" @models={{array this.channel.id (or this.channel.slug "-")}} class={{this.rowClassNames}} id={{concat "chat-channel-row-" this.channel.id}} tabindex="0" data-chat-channel-id={{this.channel.id}}>
<ChatChannelTitle @channel={{this.channel}} @unreadIndicator={{true}} />
{{#if this.showUserStatus}}
<UserStatusMessage @status={{this.channel.chatable.users.firstObject.status}} />
{{/if}}
<ChatChannelTitle @channel={{this.channel}} />
<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{true}} />
{{#if (and this.options.leaveButton this.channel.isFollowing (not this.site.mobileView))}}
<ToggleChannelMembershipButton

View File

@ -11,6 +11,6 @@
{{this.model.username}}
</span>
{{else}}
<ChatChannelTitle @channel={{this.model}} @unreadIndicator={{true}} />
<ChatChannelTitle @channel={{this.model}} />
{{/if}}
</div>

View File

@ -8,20 +8,32 @@
{{else}}
{{#if this.channel.isDirectMessageChannel}}
<div class="chat-channel-title is-dm">
{{#if this.multiDm}}
<span class="chat-channel-title__users-count">
{{this.channel.chatable.users.length}}
</span>
<span class="chat-channel-title__name">{{this.usernames}}</span>
{{else}}
<ChatUserAvatar @user={{this.channel.chatable.users.firstObject}} />
<span class="chat-channel-title__usernames">
{{#let this.channel.chatable.users.firstObject as |user|}}
<span class="chat-channel-title__name">{{user.username}}</span>
<PluginOutlet @name="after-chat-channel-username" @args={{hash user=user}} @tagName="" @connectorTagName="" />
{{/let}}
</span>
{{/if}}
<div class="chat-channel-title__avatar">
{{#if this.multiDm}}
<span class="chat-channel-title__users-count">
{{this.channel.chatable.users.length}}
</span>
{{else}}
<ChatUserAvatar @user={{this.channel.chatable.users.firstObject}} />
{{/if}}
</div>
<div class="chat-channel-title__user-info">
<div class="chat-channel-title__usernames">
{{#if this.multiDm}}
<span class="chat-channel-title__name">{{this.usernames}}</span>
{{else}}
{{#let this.channel.chatable.users.firstObject as |user|}}
<span class="chat-channel-title__name">{{user.username}}</span>
{{#if this.showUserStatus}}
<UserStatusMessage @status={{this.channel.chatable.users.firstObject.status}} @showDescription={{if this.site.mobileView "true"}} />
{{/if}}
<PluginOutlet @name="after-chat-channel-username" @args={{hash user=user}} @tagName="" @connectorTagName="" />
{{/let}}
{{/if}}
</div>
</div>
{{#if (has-block)}}
{{yield}}
@ -47,8 +59,4 @@
{{/if}}
</div>
{{/if}}
{{#if this.unreadIndicator}}
<ChatChannelUnreadIndicator @channel={{this.channel}} />
{{/if}}
{{/if}}

View File

@ -1,3 +1,5 @@
{{#if this.hasUnread}}
<span class="chat-channel-unread-indicator {{if this.isUrgent "urgent"}}"></span>
<div class="chat-channel-unread-indicator {{if this.isUrgent "urgent"}}">
<div class="number">{{this.unreadCount}}</div>
</div>
{{/if}}

View File

@ -6,6 +6,7 @@
.chat-channel-title {
display: flex;
align-items: center;
@include ellipsis;
.category-chat-private .d-icon {
background-color: var(--secondary);
@ -19,6 +20,10 @@
top: -4px;
}
.user-status-message {
display: none; // we only show when in channels list
}
.chat-name,
.category-chat-name,
&__usernames,
@ -71,10 +76,6 @@
font-size: var(--font-down-1);
align-items: center;
padding: 0.25rem 0.5rem;
& + .chat-channel-title__name {
margin-left: 0.5rem;
}
}
.chat-channel-title__category-badge {
@ -82,10 +83,6 @@
display: flex;
font-size: var(--font-up-1);
position: relative;
+ .chat-channel-title__name {
margin-left: 0.5rem;
}
}
.chat-channel-title .chat-user-avatar {
@ -120,6 +117,7 @@
@include ellipsis;
font-size: var(--font-0);
color: var(--primary);
margin-left: 0.5rem;
}
.channel-info {
@ -127,3 +125,7 @@
max-width: 100%;
}
}
.has-unread .chat-channel-title__name {
font-weight: bold;
}

View File

@ -38,22 +38,13 @@ body.composer-open .chat-drawer-outlet-container {
.channels-list {
.chat-channel-row {
padding: 0 0 0 0.5rem;
margin: 0 0.5rem 0.125rem 0.5rem;
border-radius: 0.25em;
}
height: 3.6em;
padding: 0 0.5rem;
margin: 0 0 0 0.5rem;
.chat-channel-unread-indicator {
left: 3px;
min-width: 8px;
width: 8px;
height: 8px;
border-radius: 7px;
top: calc(50% - 5px);
}
.chat-channel-title {
padding: 0.5rem;
&:not(:last-of-type) {
border-bottom: 1px solid var(--primary-low);
}
}
}
}
@ -114,7 +105,7 @@ body.composer-open .chat-drawer-outlet-container {
flex-direction: column;
width: 100%;
font-weight: 700;
padding: 0 0.5rem;
padding: 0 0.5rem 0 1rem;
cursor: pointer;
.chat-channel-title {
@ -222,6 +213,7 @@ body.composer-open .chat-drawer-outlet-container {
padding-bottom: 0.25em;
.channels-list .chat-channel-divider {
padding: 0.25rem 0.5rem 0.25rem 1rem;
padding: 1.5rem 0.5rem 1rem 1rem;
color: var(--quaternary);
}
}

View File

@ -0,0 +1,13 @@
.channels-list {
.chat-channel-divider {
padding: 2.5rem 1.5rem 0.5rem 1.5rem;
&:first-of-type {
padding-top: 1rem;
}
}
.chat-channel-title {
padding-right: 0.5em;
}
}

View File

@ -4,6 +4,7 @@ $float-height: 530px;
--message-left-width: 42px;
--full-page-border-radius: 12px;
--full-page-sidebar-width: 275px;
--channel-list-avatar-size: 30px;
--chat-header-offset: 65px;
}
@ -77,10 +78,11 @@ $float-height: 530px;
height: 14px;
border-radius: 100%;
box-sizing: content-box;
border: 2px solid var(--secondary);
-webkit-touch-callout: none;
background: var(--tertiary-med-or-tertiary);
color: var(--secondary);
font-size: var(--font-down-2);
text-align: center;
&.urgent {
background: var(--success);
@ -137,26 +139,26 @@ $float-height: 530px;
}
.chat-channel-unread-indicator {
flex-shrink: 0;
width: 10px;
height: 10px;
border-radius: 10px;
border: 0;
right: 7px;
top: calc(50% - 5px);
&.urgent .number-wrap {
display: none;
}
font-size: var(--font-down-1);
}
.open-browse-page-btn,
.open-draft-channel-page-btn,
.chat-channel-leave-btn {
position: relative;
padding: 0;
background: transparent;
color: var(--primary-medium);
font-size: var(--font-0-rem);
padding: 0.5rem;
&:after {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
}
&:hover {
background: transparent;
@ -209,10 +211,6 @@ $float-height: 530px;
background: none;
}
}
.chat-channel-title {
padding: 0.5rem;
}
}
.chat-messages-container {
@ -431,18 +429,61 @@ $float-height: 530px;
align-items: center;
box-sizing: border-box;
display: flex;
justify-content: space-between;
position: relative;
cursor: pointer;
color: var(--primary-high);
transition: opacity 50ms ease-in;
opacity: 1;
.chat-user-avatar {
pointer-events: none;
.chat-channel-title {
&__user-info {
@include ellipsis;
}
&__usernames {
display: flex;
align-items: center;
justify-content: start;
}
.user-status-message {
display: inline-block;
color: var(--primary-medium);
font-size: var(--font-down-2);
margin-right: 0.5rem;
}
}
.chat-channel-unread-indicator {
margin-left: 0.5rem;
.chat-channel-metadata {
display: flex;
align-items: flex-end;
flex-direction: column;
&__date {
color: var(--primary-high);
font-size: var(--font-down-2);
}
.chat-channel-unread-indicator {
display: flex;
align-items: center;
justify-content: center;
margin-top: 0.25rem;
width: auto;
min-width: 14px;
padding: 2px;
font-size: var(--font-down-3);
border-radius: 1em;
.number {
line-height: 1rem;
}
}
}
.chat-user-avatar {
pointer-events: none;
}
&.unfollowing {
@ -450,18 +491,22 @@ $float-height: 530px;
}
.toggle-channel-membership-button.-leave {
visibility: hidden;
display: none;
margin-left: auto;
}
&:hover {
&.can-leave:hover {
.toggle-channel-membership-button.-leave {
visibility: visible;
display: block;
> * {
pointer-events: auto;
}
}
.chat-channel-metadata {
display: none;
}
}
.discourse-no-touch &:hover,
@ -492,6 +537,10 @@ $float-height: 530px;
}
}
&:visited {
color: var(--primary-high);
}
&.muted {
opacity: 0.65;
}
@ -508,13 +557,6 @@ $float-height: 530px;
}
}
.chat-channel-row-unread-count {
display: inline-block;
margin-left: 5px;
font-size: var(--font-down-1);
color: var(--primary-high);
}
.emoji {
margin-left: 0.3em;
}
@ -972,3 +1014,22 @@ html.has-full-page-chat {
[data-popper-reference-hidden] {
visibility: hidden;
}
.channels-list {
.chat-user-avatar {
img {
width: calc(var(--channel-list-avatar-size) - 2px);
height: calc(var(--channel-list-avatar-size) - 2px);
}
}
.chat-channel-title {
&__users-count {
width: var(--channel-list-avatar-size);
height: var(--channel-list-avatar-size);
padding: 0;
font-size: var(--font-up-1);
justify-content: center;
}
}
}

View File

@ -77,7 +77,25 @@
background: var(--primary-very-low);
.chat-channel-divider {
padding: 0.5rem 0.5rem 0 1rem;
padding: 2rem 0.5rem 0.5rem 1rem;
&:first-of-type {
padding-top: 1.5rem;
}
}
.chat-channel-row {
height: 2.5em;
padding-right: 0.5rem;
.chat-channel-metadata {
&__date {
display: none;
}
.chat-channel-unread-indicator {
margin-top: 0;
}
}
}
.loading-container {

View File

@ -6,12 +6,6 @@
padding-bottom: 6rem;
box-sizing: border-box;
.direct-message-channels {
.chat-channel-title {
padding: 0.6rem 0; //minor adjustment for visual consistency with channels which dont have avatars
}
}
@media (hover: none) {
.chat-channel-row:hover {
background: transparent;
@ -23,20 +17,26 @@
}
.chat-channel-row {
height: 4em;
margin: 0 1.5rem;
padding: 0;
border-radius: 0;
border-bottom: 1px solid var(--primary-low);
.chat-channel-metadata {
&__date {
font-size: var(--font-down-2);
}
}
}
.chat-channel-divider {
background-color: var(--secondary);
padding: 2.5rem 1.5rem 0.5rem 1.5rem;
padding: 2.5rem 1.5rem 0.75rem 1.5rem;
font-size: var(--font-up-1);
&:first-of-type {
padding-top: 1rem;
padding-bottom: 0; //visual compensation
}
.channel-title {
@ -54,35 +54,24 @@
}
.chat-user-avatar {
img {
width: calc(var(--chat-mobile-avatar-size) - 2px);
height: calc(var(--chat-mobile-avatar-size) - 2px);
}
+ .chat-channel-title__usernames {
margin-left: 1rem;
}
}
.chat-channel-title {
padding: 1rem 0;
width: 100%;
overflow: hidden;
&__users-count {
width: var(--chat-mobile-avatar-size);
height: var(--chat-mobile-avatar-size);
padding: 0;
font-size: var(--font-up-2);
font-weight: normal;
justify-content: center;
& + .chat-channel-title__name {
margin-left: 1rem;
}
}
&__name {
margin-left: 0.75em;
font-size: var(--font-up-1);
}

View File

@ -1,5 +1,5 @@
:root {
--chat-mobile-avatar-size: 38px;
--channel-list-avatar-size: 38px;
}
.chat-message {
@ -73,16 +73,17 @@ body.has-full-page-chat {
}
}
.channels-list .chat-channel-row {
.category-chat-private .d-icon {
background-color: var(--secondary);
}
.channels-list {
.chat-channel-row {
.category-chat-private .d-icon {
background-color: var(--secondary);
}
.chat-channel-unread-indicator {
width: 6px;
height: 6px;
left: 5px;
top: calc(50% - 3px);
.chat-channel-metadata {
.chat-channel-unread-indicator {
font-size: var(--font-down-2);
}
}
}
}

View File

@ -49,7 +49,7 @@ en:
leave_channel: "Leave channel"
join: "Join"
leave: "Leave"
save_label:
save_label:
mute_channel: "Mute channel preference saved"
desktop_notification: "Desktop notification preference saved"
mobile_notification: "Mobile push notification preference saved"

View File

@ -17,6 +17,7 @@ register_asset "stylesheets/common/dc-filter-input.scss"
register_asset "stylesheets/common/common.scss"
register_asset "stylesheets/common/chat-browse.scss"
register_asset "stylesheets/common/chat-drawer.scss"
register_asset "stylesheets/common/chat-index.scss"
register_asset "stylesheets/mobile/chat-index.scss", :mobile
register_asset "stylesheets/common/chat-channel-preview-card.scss"
register_asset "stylesheets/common/chat-channel-info.scss"

View File

@ -0,0 +1,42 @@
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import fabricators from "../helpers/fabricators";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
setupRenderingTest(hooks);
test("displays last message sent at", async function (assert) {
const lastMessageSentAt = moment();
this.channel = fabricators.directMessageChatChannel({
last_message_sent_at: lastMessageSentAt,
});
await render(hbs`<ChatChannelMetadata @channel={{this.channel}} />`);
assert
.dom(".chat-channel-metadata__date")
.hasText(lastMessageSentAt.format("LT"));
});
test("unreadIndicator", async function (assert) {
this.channel = fabricators.directMessageChatChannel();
this.currentUser.set("chat_channel_tracking_state", {
[this.channel.id]: { unread_count: 1 },
});
this.unreadIndicator = true;
await render(
hbs`<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{this.unreadIndicator}}/>`
);
assert.ok(exists(".chat-channel-unread-indicator"));
this.unreadIndicator = false;
await render(
hbs`<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{this.unreadIndicator}}/>`
);
assert.notOk(exists(".chat-channel-unread-indicator"));
});
});

View File

@ -142,32 +142,4 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
);
},
});
componentTest("unreadIndicator", {
template: hbs`{{chat-channel-title channel=channel unreadIndicator=unreadIndicator}}`,
beforeEach() {
const channel = fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.directMessageChannel,
});
const state = {};
state[channel.id] = {
unread_count: 1,
};
this.currentUser.set("chat_channel_tracking_state", state);
this.set("channel", channel);
},
async test(assert) {
this.set("unreadIndicator", true);
assert.ok(exists(".chat-channel-unread-indicator"));
this.set("unreadIndicator", false);
assert.notOk(exists(".chat-channel-unread-indicator"));
},
});
});