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
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") @discourseComputed("active", "channel.{id,muted}", "channel.focused")
rowClassNames(active, channel, focused) { rowClassNames(active, channel, focused) {
const classes = ["chat-channel-row", `chat-channel-${channel.id}`]; const classes = ["chat-channel-row", `chat-channel-${channel.id}`];
if (active) { if (active) {
classes.push("active"); classes.push("active");
} }
@ -56,6 +57,16 @@ export default Component.extend({
if (channel.current_user_membership.muted) { if (channel.current_user_membership.muted) {
classes.push("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(" "); return classes.join(" ");
}, },

View File

@ -6,7 +6,6 @@ import { gt, reads } from "@ember/object/computed";
export default class ChatChannelTitle extends Component { export default class ChatChannelTitle extends Component {
tagName = ""; tagName = "";
channel = null; channel = null;
unreadIndicator = false;
@reads("channel.chatable.users.[]") users; @reads("channel.chatable.users.[]") users;
@gt("users.length", 1) multiDm; @gt("users.length", 1) multiDm;
@ -20,4 +19,15 @@ export default class ChatChannelTitle extends Component {
get channelColorStyle() { get channelColorStyle() {
return htmlSafe(`color: #${this.channel.chatable.color}`); 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}}> <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}} /> <ChatChannelTitle @channel={{this.channel}} />
<ChatChannelMetadata @channel={{this.channel}} @unreadIndicator={{true}} />
{{#if this.showUserStatus}}
<UserStatusMessage @status={{this.channel.chatable.users.firstObject.status}} />
{{/if}}
{{#if (and this.options.leaveButton this.channel.isFollowing (not this.site.mobileView))}} {{#if (and this.options.leaveButton this.channel.isFollowing (not this.site.mobileView))}}
<ToggleChannelMembershipButton <ToggleChannelMembershipButton

View File

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

View File

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

View File

@ -1,3 +1,5 @@
{{#if this.hasUnread}} {{#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}} {{/if}}

View File

@ -6,6 +6,7 @@
.chat-channel-title { .chat-channel-title {
display: flex; display: flex;
align-items: center; align-items: center;
@include ellipsis;
.category-chat-private .d-icon { .category-chat-private .d-icon {
background-color: var(--secondary); background-color: var(--secondary);
@ -19,6 +20,10 @@
top: -4px; top: -4px;
} }
.user-status-message {
display: none; // we only show when in channels list
}
.chat-name, .chat-name,
.category-chat-name, .category-chat-name,
&__usernames, &__usernames,
@ -71,10 +76,6 @@
font-size: var(--font-down-1); font-size: var(--font-down-1);
align-items: center; align-items: center;
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
& + .chat-channel-title__name {
margin-left: 0.5rem;
}
} }
.chat-channel-title__category-badge { .chat-channel-title__category-badge {
@ -82,10 +83,6 @@
display: flex; display: flex;
font-size: var(--font-up-1); font-size: var(--font-up-1);
position: relative; position: relative;
+ .chat-channel-title__name {
margin-left: 0.5rem;
}
} }
.chat-channel-title .chat-user-avatar { .chat-channel-title .chat-user-avatar {
@ -120,6 +117,7 @@
@include ellipsis; @include ellipsis;
font-size: var(--font-0); font-size: var(--font-0);
color: var(--primary); color: var(--primary);
margin-left: 0.5rem;
} }
.channel-info { .channel-info {
@ -127,3 +125,7 @@
max-width: 100%; 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 { .channels-list {
.chat-channel-row { .chat-channel-row {
padding: 0 0 0 0.5rem; height: 3.6em;
margin: 0 0.5rem 0.125rem 0.5rem; padding: 0 0.5rem;
border-radius: 0.25em; margin: 0 0 0 0.5rem;
}
.chat-channel-unread-indicator { &:not(:last-of-type) {
left: 3px; border-bottom: 1px solid var(--primary-low);
min-width: 8px; }
width: 8px;
height: 8px;
border-radius: 7px;
top: calc(50% - 5px);
}
.chat-channel-title {
padding: 0.5rem;
} }
} }
} }
@ -114,7 +105,7 @@ body.composer-open .chat-drawer-outlet-container {
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
font-weight: 700; font-weight: 700;
padding: 0 0.5rem; padding: 0 0.5rem 0 1rem;
cursor: pointer; cursor: pointer;
.chat-channel-title { .chat-channel-title {
@ -222,6 +213,7 @@ body.composer-open .chat-drawer-outlet-container {
padding-bottom: 0.25em; padding-bottom: 0.25em;
.channels-list .chat-channel-divider { .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; --message-left-width: 42px;
--full-page-border-radius: 12px; --full-page-border-radius: 12px;
--full-page-sidebar-width: 275px; --full-page-sidebar-width: 275px;
--channel-list-avatar-size: 30px;
--chat-header-offset: 65px; --chat-header-offset: 65px;
} }
@ -77,10 +78,11 @@ $float-height: 530px;
height: 14px; height: 14px;
border-radius: 100%; border-radius: 100%;
box-sizing: content-box; box-sizing: content-box;
border: 2px solid var(--secondary);
-webkit-touch-callout: none; -webkit-touch-callout: none;
background: var(--tertiary-med-or-tertiary); background: var(--tertiary-med-or-tertiary);
color: var(--secondary);
font-size: var(--font-down-2); font-size: var(--font-down-2);
text-align: center;
&.urgent { &.urgent {
background: var(--success); background: var(--success);
@ -137,26 +139,26 @@ $float-height: 530px;
} }
.chat-channel-unread-indicator { .chat-channel-unread-indicator {
flex-shrink: 0; font-size: var(--font-down-1);
width: 10px;
height: 10px;
border-radius: 10px;
border: 0;
right: 7px;
top: calc(50% - 5px);
&.urgent .number-wrap {
display: none;
}
} }
.open-browse-page-btn, .open-browse-page-btn,
.open-draft-channel-page-btn, .open-draft-channel-page-btn,
.chat-channel-leave-btn { .chat-channel-leave-btn {
position: relative;
padding: 0;
background: transparent; background: transparent;
color: var(--primary-medium); color: var(--primary-medium);
font-size: var(--font-0-rem); font-size: var(--font-0-rem);
padding: 0.5rem;
&:after {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
}
&:hover { &:hover {
background: transparent; background: transparent;
@ -209,10 +211,6 @@ $float-height: 530px;
background: none; background: none;
} }
} }
.chat-channel-title {
padding: 0.5rem;
}
} }
.chat-messages-container { .chat-messages-container {
@ -431,18 +429,61 @@ $float-height: 530px;
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
justify-content: space-between;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
color: var(--primary-high); color: var(--primary-high);
transition: opacity 50ms ease-in; transition: opacity 50ms ease-in;
opacity: 1; opacity: 1;
.chat-user-avatar { .chat-channel-title {
pointer-events: none; &__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 { .chat-channel-metadata {
margin-left: 0.5rem; 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 { &.unfollowing {
@ -450,18 +491,22 @@ $float-height: 530px;
} }
.toggle-channel-membership-button.-leave { .toggle-channel-membership-button.-leave {
visibility: hidden; display: none;
margin-left: auto; margin-left: auto;
} }
&:hover { &.can-leave:hover {
.toggle-channel-membership-button.-leave { .toggle-channel-membership-button.-leave {
visibility: visible; display: block;
> * { > * {
pointer-events: auto; pointer-events: auto;
} }
} }
.chat-channel-metadata {
display: none;
}
} }
.discourse-no-touch &:hover, .discourse-no-touch &:hover,
@ -492,6 +537,10 @@ $float-height: 530px;
} }
} }
&:visited {
color: var(--primary-high);
}
&.muted { &.muted {
opacity: 0.65; 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 { .emoji {
margin-left: 0.3em; margin-left: 0.3em;
} }
@ -972,3 +1014,22 @@ html.has-full-page-chat {
[data-popper-reference-hidden] { [data-popper-reference-hidden] {
visibility: 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); background: var(--primary-very-low);
.chat-channel-divider { .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 { .loading-container {

View File

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

View File

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

View File

@ -49,7 +49,7 @@ en:
leave_channel: "Leave channel" leave_channel: "Leave channel"
join: "Join" join: "Join"
leave: "Leave" leave: "Leave"
save_label: save_label:
mute_channel: "Mute channel preference saved" mute_channel: "Mute channel preference saved"
desktop_notification: "Desktop notification preference saved" desktop_notification: "Desktop notification preference saved"
mobile_notification: "Mobile push 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/common.scss"
register_asset "stylesheets/common/chat-browse.scss" register_asset "stylesheets/common/chat-browse.scss"
register_asset "stylesheets/common/chat-drawer.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/mobile/chat-index.scss", :mobile
register_asset "stylesheets/common/chat-channel-preview-card.scss" register_asset "stylesheets/common/chat-channel-preview-card.scss"
register_asset "stylesheets/common/chat-channel-info.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"));
},
});
}); });