Make chat prettier (to my eyes at least)

This commit is contained in:
Conrad Irwin 2024-01-13 21:37:13 -07:00
parent c2ff9fe2da
commit f6ef07e716
4 changed files with 101 additions and 30 deletions

View file

@ -144,7 +144,7 @@ impl ChannelChat {
message: MessageParams,
cx: &mut ModelContext<Self>,
) -> Result<Task<Result<u64>>> {
if message.text.is_empty() {
if message.text.trim().is_empty() {
Err(anyhow!("message body can't be empty"))?;
}
@ -174,6 +174,8 @@ impl ChannelChat {
let user_store = self.user_store.clone();
let rpc = self.rpc.clone();
let outgoing_messages_lock = self.outgoing_messages_lock.clone();
// todo - handle messages that fail to send (e.g. >1024 chars)
Ok(cx.spawn(move |this, mut cx| async move {
let outgoing_message_guard = outgoing_messages_lock.lock().await;
let request = rpc.request(proto::SendChannelMessage {

View file

@ -8,8 +8,9 @@ use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use gpui::{
actions, div, list, prelude::*, px, Action, AnyElement, AppContext, AsyncWindowContext,
ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, ListOffset, ListScrollEvent,
ListState, Model, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView,
ClickEvent, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight,
ListOffset, ListScrollEvent, ListState, Model, Render, Subscription, Task, View, ViewContext,
VisualContext, WeakView,
};
use language::LanguageRegistry;
use menu::Confirm;
@ -22,7 +23,8 @@ use settings::{Settings, SettingsStore};
use std::sync::Arc;
use time::{OffsetDateTime, UtcOffset};
use ui::{
prelude::*, Avatar, Button, IconButton, IconName, Key, KeyBinding, Label, TabBar, Tooltip,
popover_menu, prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, Key, KeyBinding,
Label, TabBar, Tooltip,
};
use util::{ResultExt, TryFutureExt};
use workspace::{
@ -60,6 +62,7 @@ pub struct ChatPanel {
is_scrolled_to_bottom: bool,
markdown_data: HashMap<ChannelMessageId, RichText>,
focus_handle: FocusHandle,
open_context_menu: Option<(u64, Subscription)>,
}
#[derive(Serialize, Deserialize)]
@ -128,6 +131,7 @@ impl ChatPanel {
width: None,
markdown_data: Default::default(),
focus_handle: cx.focus_handle(),
open_context_menu: None,
};
let mut old_dock_position = this.position(cx);
@ -348,50 +352,100 @@ impl ChatPanel {
ChannelMessageId::Saved(id) => ("saved-message", id).into(),
ChannelMessageId::Pending(id) => ("pending-message", id).into(),
};
let this = cx.view().clone();
v_stack()
.w_full()
.id(element_id)
.relative()
.overflow_hidden()
.group("")
.when(!is_continuation_from_previous, |this| {
this.child(
this.pt_3().child(
h_stack()
.gap_2()
.child(Avatar::new(message.sender.avatar_uri.clone()))
.child(Label::new(message.sender.github_login.clone()))
.child(
div().absolute().child(
Avatar::new(message.sender.avatar_uri.clone())
.size(cx.rem_size() * 1.5),
),
)
.child(
div()
.pl(cx.rem_size() * 1.5 + px(6.0))
.pr(px(8.0))
.font_weight(FontWeight::BOLD)
.child(Label::new(message.sender.github_login.clone())),
)
.child(
Label::new(format_timestamp(
message.timestamp,
now,
self.local_timezone,
))
.size(LabelSize::Small)
.color(Color::Muted),
),
)
})
.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))
.when(is_continuation_from_previous, |this| this.pt_1())
.child(
div()
.absolute()
.top_1()
.right_2()
.w_8()
.visible_on_hover("")
.children(message_id_to_remove.map(|message_id| {
IconButton::new(("remove", message_id), IconName::XCircle).on_click(
cx.listener(move |this, _, cx| {
this.remove_message(message_id, cx);
}),
)
})),
v_stack()
.w_full()
.text_ui_sm()
.id(element_id)
.group("")
.child(text.element("body".into(), cx))
.child(
div()
.absolute()
.z_index(1)
.right_0()
.w_6()
.bg(cx.theme().colors().panel_background)
.when(!self.has_open_menu(message_id_to_remove), |el| {
el.visible_on_hover("")
})
.children(message_id_to_remove.map(|message_id| {
popover_menu(("menu", message_id))
.trigger(IconButton::new(
("trigger", message_id),
IconName::Ellipsis,
))
.menu(move |cx| {
Some(Self::render_message_menu(&this, message_id, cx))
})
})),
),
)
}
fn has_open_menu(&self, message_id: Option<u64>) -> bool {
match self.open_context_menu.as_ref() {
Some((id, _)) => Some(*id) == message_id,
None => false,
}
}
fn render_message_menu(
this: &View<Self>,
message_id: u64,
cx: &mut WindowContext,
) -> View<ContextMenu> {
let menu = {
let this = this.clone();
ContextMenu::build(cx, move |menu, _| {
menu.entry("Delete message", None, move |cx| {
this.update(cx, |this, cx| this.remove_message(message_id, cx))
})
})
};
this.update(cx, |this, cx| {
let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| {
this.open_context_menu = None;
});
this.open_context_menu = Some((message_id, subscription));
});
menu
}
fn render_markdown_with_mentions(
language_registry: &Arc<LanguageRegistry>,
current_user_id: u64,

View file

@ -1,7 +1,7 @@
use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
SharedString, StyleRefinement, Visibility, WhiteSpace,
DefiniteLength, Display, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length,
Position, SharedString, StyleRefinement, Visibility, WhiteSpace,
};
use crate::{BoxShadow, TextStyleRefinement};
use smallvec::{smallvec, SmallVec};
@ -494,6 +494,13 @@ pub trait Styled: Sized {
self
}
fn font_weight(mut self, weight: FontWeight) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
.font_weight = Some(weight);
self
}
fn text_bg(mut self, bg: impl Into<Hsla>) -> Self {
self.text_style()
.get_or_insert_with(Default::default)

View file

@ -26,6 +26,7 @@ pub enum AvatarShape {
#[derive(IntoElement)]
pub struct Avatar {
image: Img,
size: Option<Pixels>,
border_color: Option<Hsla>,
is_available: Option<bool>,
}
@ -36,7 +37,7 @@ impl RenderOnce for Avatar {
self = self.shape(AvatarShape::Circle);
}
let size = cx.rem_size();
let size = self.size.unwrap_or_else(|| cx.rem_size());
div()
.size(size + px(2.))
@ -78,6 +79,7 @@ impl Avatar {
image: img(src),
is_available: None,
border_color: None,
size: None,
}
}
@ -124,4 +126,10 @@ impl Avatar {
self.is_available = is_available.into();
self
}
/// Size overrides the avatar size. By default they are 1rem.
pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.size = size.into();
self
}
}