Improve chat panel styling (#3758)

This PR improves the chat panel styling, especially with regards to the
spacing.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2023-12-21 12:15:11 -05:00 committed by GitHub
parent 7a9c4057a7
commit 824b68788f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 98 deletions

View file

@ -21,7 +21,7 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc; use std::sync::Arc;
use theme::ActiveTheme as _; use theme::ActiveTheme as _;
use time::{OffsetDateTime, UtcOffset}; use time::{OffsetDateTime, UtcOffset};
use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, Tooltip}; use ui::{prelude::*, Avatar, Button, Icon, IconButton, Label, TabBar, Tooltip};
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::{ use workspace::{
dock::{DockPosition, Panel, PanelEvent}, dock::{DockPosition, Panel, PanelEvent},
@ -97,7 +97,9 @@ impl ChatPanel {
let message_list = let message_list =
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| { ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
if let Some(view) = view.upgrade() { if let Some(view) = view.upgrade() {
view.update(cx, |view, cx| view.render_message(ix, cx)) view.update(cx, |view, cx| {
view.render_message(ix, cx).into_any_element()
})
} else { } else {
div().into_any() div().into_any()
} }
@ -263,37 +265,41 @@ impl ChatPanel {
.full() .full()
.on_action(cx.listener(Self::send)) .on_action(cx.listener(Self::send))
.child( .child(
h_stack() h_stack().z_index(1).child(
.w_full() TabBar::new("chat_header")
.h_7() .child(
.justify_between() h_stack()
.z_index(1) .w_full()
.bg(cx.theme().colors().background) .h(rems(ui::Tab::HEIGHT_IN_REMS))
.child(Label::new( .px_2()
self.active_chat .child(Label::new(
.as_ref() self.active_chat
.and_then(|c| Some(format!("#{}", c.0.read(cx).channel(cx)?.name))) .as_ref()
.unwrap_or_default(), .and_then(|c| {
)) Some(format!("#{}", c.0.read(cx).channel(cx)?.name))
.child( })
h_stack() .unwrap_or_default(),
.child( )),
IconButton::new("notes", Icon::File) )
.on_click(cx.listener(Self::open_notes)) .end_child(
.tooltip(|cx| Tooltip::text("Open notes", cx)), IconButton::new("notes", Icon::File)
) .on_click(cx.listener(Self::open_notes))
.child( .tooltip(|cx| Tooltip::text("Open notes", cx)),
IconButton::new("call", Icon::AudioOn) )
.on_click(cx.listener(Self::join_call)) .end_child(
.tooltip(|cx| Tooltip::text("Join call", cx)), IconButton::new("call", Icon::AudioOn)
), .on_click(cx.listener(Self::join_call))
), .tooltip(|cx| Tooltip::text("Join call", cx)),
) ),
.child( ),
div()
.flex_grow()
.child(self.render_active_channel_messages(cx)),
) )
.child(div().flex_grow().px_2().py_1().map(|this| {
if self.active_chat.is_some() {
this.child(list(self.message_list.clone()).full())
} else {
this
}
}))
.child( .child(
div() div()
.z_index(1) .z_index(1)
@ -304,39 +310,42 @@ impl ChatPanel {
.into_any() .into_any()
} }
fn render_active_channel_messages(&self, _cx: &mut ViewContext<Self>) -> AnyElement { fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
if self.active_chat.is_some() {
list(self.message_list.clone()).full().into_any_element()
} else {
div().into_any_element()
}
}
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
let active_chat = &self.active_chat.as_ref().unwrap().0; let active_chat = &self.active_chat.as_ref().unwrap().0;
let (message, is_continuation, is_admin) = active_chat.update(cx, |active_chat, cx| { let (message, is_continuation_from_previous, is_continuation_to_next, is_admin) =
let is_admin = self active_chat.update(cx, |active_chat, cx| {
.channel_store let is_admin = self
.read(cx) .channel_store
.is_channel_admin(active_chat.channel_id); .read(cx)
.is_channel_admin(active_chat.channel_id);
let last_message = active_chat.message(ix.saturating_sub(1)); let last_message = active_chat.message(ix.saturating_sub(1));
let this_message = active_chat.message(ix).clone(); let this_message = active_chat.message(ix).clone();
let is_continuation = last_message.id != this_message.id let next_message =
&& this_message.sender.id == last_message.sender.id; active_chat.message(ix.saturating_add(1).min(active_chat.message_count() - 1));
if let ChannelMessageId::Saved(id) = this_message.id { let is_continuation_from_previous = last_message.id != this_message.id
if this_message && last_message.sender.id == this_message.sender.id;
.mentions let is_continuation_to_next = this_message.id != next_message.id
.iter() && this_message.sender.id == next_message.sender.id;
.any(|(_, user_id)| Some(*user_id) == self.client.user_id())
{ if let ChannelMessageId::Saved(id) = this_message.id {
active_chat.acknowledge_message(id); if this_message
.mentions
.iter()
.any(|(_, user_id)| Some(*user_id) == self.client.user_id())
{
active_chat.acknowledge_message(id);
}
} }
}
(this_message, is_continuation, is_admin) (
}); this_message,
is_continuation_from_previous,
is_continuation_to_next,
is_admin,
)
});
let _is_pending = message.is_pending(); let _is_pending = message.is_pending();
let text = self.markdown_data.entry(message.id).or_insert_with(|| { let text = self.markdown_data.entry(message.id).or_insert_with(|| {
@ -359,27 +368,31 @@ impl ChatPanel {
ChannelMessageId::Pending(id) => ("pending-message", id).into(), ChannelMessageId::Pending(id) => ("pending-message", id).into(),
}; };
let mut result = v_stack() v_stack()
.w_full() .w_full()
.id(element_id) .id(element_id)
.relative() .relative()
.overflow_hidden()
.group("") .group("")
.mb_1(); .when(!is_continuation_from_previous, |this| {
this.child(
if !is_continuation { h_stack()
result = result.child( .gap_2()
h_stack() .child(Avatar::new(message.sender.avatar_uri.clone()))
.child(Avatar::new(message.sender.avatar_uri.clone())) .child(Label::new(message.sender.github_login.clone()))
.child(Label::new(message.sender.github_login.clone())) .child(
.child(Label::new(format_timestamp( Label::new(format_timestamp(
message.timestamp, message.timestamp,
now, now,
self.local_timezone, self.local_timezone,
))), ))
); .color(Color::Muted),
} ),
)
result })
.when(!is_continuation_to_next, |this|
// HACK: This should really be a margin, but margins seem to get collapsed.
this.pb_2())
.child(text.element("body".into(), cx)) .child(text.element("body".into(), cx))
.child( .child(
div() div()
@ -396,7 +409,6 @@ impl ChatPanel {
) )
})), })),
) )
.into_any()
} }
fn render_markdown_with_mentions( fn render_markdown_with_mentions(

View file

@ -102,16 +102,18 @@ impl RenderOnce for TabBar {
.w_full() .w_full()
.h(rems(HEIGHT_IN_REMS)) .h(rems(HEIGHT_IN_REMS))
.bg(cx.theme().colors().tab_bar_background) .bg(cx.theme().colors().tab_bar_background)
.child( .when(!self.start_children.is_empty(), |this| {
h_stack() this.child(
.flex_none() h_stack()
.gap_1() .flex_none()
.px_1() .gap_1()
.border_b() .px_1()
.border_r() .border_b()
.border_color(cx.theme().colors().border) .border_r()
.children(self.start_children), .border_color(cx.theme().colors().border)
) .children(self.start_children),
)
})
.child( .child(
div() div()
.relative() .relative()
@ -140,15 +142,17 @@ impl RenderOnce for TabBar {
.children(self.children), .children(self.children),
), ),
) )
.child( .when(!self.end_children.is_empty(), |this| {
h_stack() this.child(
.flex_none() h_stack()
.gap_1() .flex_none()
.px_1() .gap_1()
.border_b() .px_1()
.border_l() .border_b()
.border_color(cx.theme().colors().border) .border_l()
.children(self.end_children), .border_color(cx.theme().colors().border)
) .children(self.end_children),
)
})
} }
} }