From 5074bccae4fbdcb25c02920a2c43cbfc4c447a96 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 4 Oct 2023 14:04:02 -0700 Subject: [PATCH 1/3] Add image avatars to channel messages --- crates/collab_ui/src/chat_panel.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index f0874f544e..8959fb6932 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -2,7 +2,7 @@ use crate::{channel_view::ChannelView, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; -use client::Client; +use client::{Client, UserStore}; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; @@ -35,6 +35,7 @@ const CHAT_PANEL_KEY: &'static str = "ChatPanel"; pub struct ChatPanel { client: Arc, channel_store: ModelHandle, + user_store: ModelHandle, active_chat: Option<(ModelHandle, Subscription)>, message_list: ListState, input_editor: ViewHandle, @@ -78,6 +79,7 @@ impl ChatPanel { let fs = workspace.app_state().fs.clone(); let client = workspace.app_state().client.clone(); let channel_store = workspace.app_state().channel_store.clone(); + let user_store = workspace.app_state().user_store.clone(); let input_editor = cx.add_view(|cx| { let mut editor = Editor::auto_height( @@ -130,6 +132,7 @@ impl ChatPanel { fs, client, channel_store, + user_store, active_chat: Default::default(), pending_serialization: Task::ready(None), message_list, @@ -352,6 +355,27 @@ impl ChatPanel { Flex::column() .with_child( Flex::row() + .with_child( + message + .sender + .avatar + .clone() + .map(|avatar| { + Image::from_data(avatar) + .with_style(theme.collab_panel.channel_avatar) + .into_any() + }) + .unwrap_or_else(|| { + Empty::new() + .constrained() + .with_width( + theme.collab_panel.channel_avatar.width.unwrap_or(12.), + ) + .into_any() + }) + .contained() + .with_margin_right(4.), + ) .with_child( Label::new( message.sender.github_login.clone(), @@ -386,7 +410,8 @@ impl ChatPanel { this.remove_message(id, cx); }) .flex_float() - })), + })) + .align_children_center(), ) .with_child(Text::new(body, style.body.clone())) .contained() From 73e78a2257c46c5f9c636824f8b193d7743e3bf0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 4 Oct 2023 14:29:08 -0700 Subject: [PATCH 2/3] Adjust channel rendering to group related messages --- crates/collab_ui/src/chat_panel.rs | 178 +++++++++++++++++----------- crates/theme/src/theme.rs | 2 + styles/src/style_tree/chat_panel.ts | 14 ++- 3 files changed, 126 insertions(+), 68 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 8959fb6932..41bc5fbd08 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -2,7 +2,7 @@ use crate::{channel_view::ChannelView, ChatPanelSettings}; use anyhow::Result; use call::ActiveCall; use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore}; -use client::{Client, UserStore}; +use client::Client; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt}; @@ -35,7 +35,6 @@ const CHAT_PANEL_KEY: &'static str = "ChatPanel"; pub struct ChatPanel { client: Arc, channel_store: ModelHandle, - user_store: ModelHandle, active_chat: Option<(ModelHandle, Subscription)>, message_list: ListState, input_editor: ViewHandle, @@ -79,7 +78,6 @@ impl ChatPanel { let fs = workspace.app_state().fs.clone(); let client = workspace.app_state().client.clone(); let channel_store = workspace.app_state().channel_store.clone(); - let user_store = workspace.app_state().user_store.clone(); let input_editor = cx.add_view(|cx| { let mut editor = Editor::auto_height( @@ -132,7 +130,7 @@ impl ChatPanel { fs, client, channel_store, - user_store, + active_chat: Default::default(), pending_serialization: Task::ready(None), message_list, @@ -331,12 +329,26 @@ impl ChatPanel { } fn render_message(&self, ix: usize, cx: &mut ViewContext) -> AnyElement { - let message = self.active_chat.as_ref().unwrap().0.read(cx).message(ix); + let (message, is_continuation, is_last) = { + let active_chat = self.active_chat.as_ref().unwrap().0.read(cx); + let last_message = active_chat.message(ix.saturating_sub(1)); + let this_message = active_chat.message(ix); + let is_continuation = last_message.id != this_message.id + && this_message.sender.id == last_message.sender.id; + + ( + active_chat.message(ix), + is_continuation, + active_chat.message_count() == ix + 1, + ) + }; let now = OffsetDateTime::now_utc(); let theme = theme::current(cx); let style = if message.is_pending() { &theme.chat_panel.pending_message + } else if is_continuation { + &theme.chat_panel.continuation_message } else { &theme.chat_panel.message }; @@ -352,71 +364,103 @@ impl ChatPanel { enum DeleteMessage {} let body = message.body.clone(); - Flex::column() - .with_child( - Flex::row() - .with_child( - message - .sender - .avatar - .clone() - .map(|avatar| { - Image::from_data(avatar) - .with_style(theme.collab_panel.channel_avatar) - .into_any() - }) - .unwrap_or_else(|| { - Empty::new() - .constrained() - .with_width( - theme.collab_panel.channel_avatar.width.unwrap_or(12.), - ) - .into_any() - }) + if is_continuation { + Flex::row() + .with_child(Text::new(body, style.body.clone())) + .with_children(message_id_to_remove.map(|id| { + MouseEventHandler::new::(id as usize, cx, |mouse_state, _| { + let button_style = theme.chat_panel.icon_button.style_for(mouse_state); + render_icon_button(button_style, "icons/x.svg") + .aligned() + .into_any() + }) + .with_padding(Padding::uniform(2.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, this, cx| { + this.remove_message(id, cx); + }) + .flex_float() + })) + .contained() + .with_style(style.container) + .with_margin_bottom(if is_last { + theme.chat_panel.last_message_bottom_spacing + } else { + 0. + }) + .into_any() + } else { + Flex::column() + .with_child( + Flex::row() + .with_child( + message + .sender + .avatar + .clone() + .map(|avatar| { + Image::from_data(avatar) + .with_style(theme.collab_panel.channel_avatar) + .into_any() + }) + .unwrap_or_else(|| { + Empty::new() + .constrained() + .with_width( + theme.collab_panel.channel_avatar.width.unwrap_or(12.), + ) + .into_any() + }) + .contained() + .with_margin_right(4.), + ) + .with_child( + Label::new( + message.sender.github_login.clone(), + style.sender.text.clone(), + ) .contained() - .with_margin_right(4.), - ) - .with_child( - Label::new( - message.sender.github_login.clone(), - style.sender.text.clone(), + .with_style(style.sender.container), ) - .contained() - .with_style(style.sender.container), - ) - .with_child( - Label::new( - format_timestamp(message.timestamp, now, self.local_timezone), - style.timestamp.text.clone(), + .with_child( + Label::new( + format_timestamp(message.timestamp, now, self.local_timezone), + style.timestamp.text.clone(), + ) + .contained() + .with_style(style.timestamp.container), ) - .contained() - .with_style(style.timestamp.container), - ) - .with_children(message_id_to_remove.map(|id| { - MouseEventHandler::new::( - id as usize, - cx, - |mouse_state, _| { - let button_style = - theme.chat_panel.icon_button.style_for(mouse_state); - render_icon_button(button_style, "icons/x.svg") - .aligned() - .into_any() - }, - ) - .with_padding(Padding::uniform(2.)) - .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, move |_, this, cx| { - this.remove_message(id, cx); - }) - .flex_float() - })) - .align_children_center(), - ) - .with_child(Text::new(body, style.body.clone())) - .contained() - .with_style(style.container) - .into_any() + .with_children(message_id_to_remove.map(|id| { + MouseEventHandler::new::( + id as usize, + cx, + |mouse_state, _| { + let button_style = + theme.chat_panel.icon_button.style_for(mouse_state); + render_icon_button(button_style, "icons/x.svg") + .aligned() + .into_any() + }, + ) + .with_padding(Padding::uniform(2.)) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, move |_, this, cx| { + this.remove_message(id, cx); + }) + .flex_float() + })) + .align_children_center(), + ) + .with_child(Text::new(body, style.body.clone())) + .contained() + .with_style(style.container) + .with_margin_bottom(if is_last { + theme.chat_panel.last_message_bottom_spacing + } else { + 0. + }) + .into_any() + } } fn render_input_box(&self, theme: &Arc, cx: &AppContext) -> AnyElement { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9b7c0309c6..63241668c4 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -635,6 +635,8 @@ pub struct ChatPanel { pub channel_select: ChannelSelect, pub input_editor: FieldEditor, pub message: ChatMessage, + pub continuation_message: ChatMessage, + pub last_message_bottom_spacing: f32, pub pending_message: ChatMessage, pub sign_in_prompt: Interactive, pub icon_button: Interactive, diff --git a/styles/src/style_tree/chat_panel.ts b/styles/src/style_tree/chat_panel.ts index 9efa084456..466d25f43d 100644 --- a/styles/src/style_tree/chat_panel.ts +++ b/styles/src/style_tree/chat_panel.ts @@ -87,7 +87,19 @@ export default function chat_panel(): any { ...text(layer, "sans", "base", { weight: "bold" }), }, timestamp: text(layer, "sans", "base", "disabled"), - margin: { bottom: SPACING } + margin: { top: SPACING } + }, + last_message_bottom_spacing: SPACING, + continuation_message: { + body: text(layer, "sans", "base"), + sender: { + margin: { + right: 8, + }, + ...text(layer, "sans", "base", { weight: "bold" }), + }, + timestamp: text(layer, "sans", "base", "disabled"), + }, pending_message: { body: text(layer, "sans", "base"), From 2f3c3d510fbcae4413dad5611db68ea63a3f2aa5 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 4 Oct 2023 14:43:58 -0700 Subject: [PATCH 3/3] Fix hit boxes and hover styles for new buttons co-authored-by: conrad --- crates/collab_ui/src/collab_panel.rs | 35 ++++++++++++++++++++----- styles/src/style_tree/component_test.ts | 1 + 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 39543c8def..7bcaa5be66 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1937,6 +1937,8 @@ impl CollabPanel { is_dragged_over = true; } + let has_messages_notification = channel.unseen_message_id.is_some(); + MouseEventHandler::new::(ix, cx, |state, cx| { let row_hovered = state.hovered(); @@ -2022,24 +2024,33 @@ impl CollabPanel { .flex(1., true) }) .with_child( - MouseEventHandler::new::(ix, cx, move |_, _| { + MouseEventHandler::new::(ix, cx, move |mouse_state, _| { + let container_style = collab_theme + .disclosure + .button + .style_for(mouse_state) + .container; + if channel.unseen_message_id.is_some() { Svg::new("icons/conversations.svg") .with_color(collab_theme.channel_note_active_color) .constrained() .with_width(collab_theme.channel_hash.width) + .contained() + .with_style(container_style) + .with_uniform_padding(4.) .into_any() } else if row_hovered { Svg::new("icons/conversations.svg") .with_color(collab_theme.channel_hash.color) .constrained() .with_width(collab_theme.channel_hash.width) + .contained() + .with_style(container_style) + .with_uniform_padding(4.) .into_any() } else { - Empty::new() - .constrained() - .with_width(collab_theme.channel_hash.width) - .into_any() + Empty::new().into_any() } }) .on_click(MouseButton::Left, move |_, this, cx| { @@ -2056,7 +2067,12 @@ impl CollabPanel { .with_margin_right(4.), ) .with_child( - MouseEventHandler::new::(ix, cx, move |_, cx| { + MouseEventHandler::new::(ix, cx, move |mouse_state, cx| { + let container_style = collab_theme + .disclosure + .button + .style_for(mouse_state) + .container; if row_hovered || channel.unseen_note_version.is_some() { Svg::new("icons/file.svg") .with_color(if channel.unseen_note_version.is_some() { @@ -2067,6 +2083,8 @@ impl CollabPanel { .constrained() .with_width(collab_theme.channel_hash.width) .contained() + .with_style(container_style) + .with_uniform_padding(4.) .with_margin_right(collab_theme.channel_hash.container.margin.left) .with_tooltip::( ix as usize, @@ -2076,13 +2094,16 @@ impl CollabPanel { cx, ) .into_any() - } else { + } else if has_messages_notification { Empty::new() .constrained() .with_width(collab_theme.channel_hash.width) .contained() + .with_uniform_padding(4.) .with_margin_right(collab_theme.channel_hash.container.margin.left) .into_any() + } else { + Empty::new().into_any() } }) .on_click(MouseButton::Left, move |_, this, cx| { diff --git a/styles/src/style_tree/component_test.ts b/styles/src/style_tree/component_test.ts index 71057c67ea..8dc22eec31 100644 --- a/styles/src/style_tree/component_test.ts +++ b/styles/src/style_tree/component_test.ts @@ -21,6 +21,7 @@ export default function contacts_panel(): any { ...text(theme.lowest, "sans", "base"), button: icon_button({ variant: "ghost" }), spacing: 4, + padding: 4, }, } }