diff --git a/crates/collab_ui2/src/chat_panel.rs b/crates/collab_ui2/src/chat_panel.rs index a7883529a8..73d8bd6775 100644 --- a/crates/collab_ui2/src/chat_panel.rs +++ b/crates/collab_ui2/src/chat_panel.rs @@ -8,8 +8,8 @@ use db::kvp::KEY_VALUE_STORE; use editor::Editor; use gpui::{ actions, div, list, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext, - Div, EventEmitter, FocusableView, ListState, Model, Render, Subscription, Task, View, - ViewContext, VisualContext, WeakView, + ClickEvent, Div, EventEmitter, FocusableView, ListOffset, ListScrollEvent, ListState, Model, + Render, SharedString, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::LanguageRegistry; use menu::Confirm; @@ -20,7 +20,10 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::sync::Arc; use time::{OffsetDateTime, UtcOffset}; -use ui::prelude::WindowContext; +use ui::{ + h_stack, prelude::WindowContext, v_stack, Avatar, Button, ButtonCommon as _, Clickable, Icon, + IconButton, Label, Tooltip, +}; use util::{ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, @@ -47,7 +50,6 @@ pub struct ChatPanel { subscriptions: Vec, workspace: WeakView, is_scrolled_to_bottom: bool, - has_focus: bool, markdown_data: HashMap, } @@ -63,11 +65,10 @@ pub enum Event { Dismissed, } -actions!(LoadMoreMessages, ToggleFocus, OpenChannelNotes, JoinCall); +actions!(ToggleFocus, OpenChannelNotes, JoinCall); // pub fn init(cx: &mut AppContext) { // cx.add_action(ChatPanel::send); -// cx.add_action(ChatPanel::load_more_messages); // cx.add_action(ChatPanel::open_notes); // cx.add_action(ChatPanel::join_call); // } @@ -121,12 +122,12 @@ impl ChatPanel { view.update(cx, |view, cx| view.render_message(ix, cx)) }); - // message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| { - // if event.visible_range.start < MESSAGE_LOADING_THRESHOLD { - // this.load_more_messages(cx); - // } - // this.is_scrolled_to_bottom = event.visible_range.end == event.count; - // })); + message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| { + if event.visible_range.start < MESSAGE_LOADING_THRESHOLD { + this.load_more_messages(cx); + } + this.is_scrolled_to_bottom = event.visible_range.end == event.count; + })); let mut this = Self { fs, @@ -138,7 +139,6 @@ impl ChatPanel { pending_serialization: Task::ready(None), input_editor, local_timezone: cx.local_timezone(), - has_focus: false, subscriptions: Vec::new(), workspace: workspace_handle, is_scrolled_to_bottom: true, @@ -311,19 +311,39 @@ impl ChatPanel { } fn render_channel(&self, cx: &mut ViewContext) -> AnyElement { - todo!() - // v_stack() - // .child(Label::new( - // self.active_chat.map_or(Default::default(), |c| { - // c.0.read(cx).channel(cx)?.name.clone() - // }), - // )) - // .child(self.render_active_channel_messages(cx)) - // .child(self.input_editor.clone()) - // .into_any() + v_stack() + .full() + .on_action(cx.listener(Self::send)) + .child( + h_stack() + .w_full() + .justify_between() + .child(Label::new( + self.active_chat + .as_ref() + .and_then(|c| Some(c.0.read(cx).channel(cx)?.name.clone())) + .unwrap_or_default(), + )) + .child( + h_stack() + .child( + IconButton::new("notes", Icon::File) + .on_click(cx.listener(Self::open_notes)) + .tooltip(|cx| Tooltip::text("Open notes", cx)), + ) + .child( + IconButton::new("call", Icon::AudioOn) + .on_click(cx.listener(Self::join_call)) + .tooltip(|cx| Tooltip::text("Join call", cx)), + ), + ), + ) + .child(div().grow().child(self.render_active_channel_messages(cx))) + .child(self.input_editor.clone()) + .into_any() } - fn render_active_channel_messages(&self, cx: &mut ViewContext) -> AnyElement { + fn render_active_channel_messages(&self, _cx: &mut ViewContext) -> AnyElement { if self.active_chat.is_some() { list(self.message_list.clone()).into_any_element() } else { @@ -332,88 +352,81 @@ impl ChatPanel { } fn render_message(&mut self, ix: usize, cx: &mut ViewContext) -> AnyElement { - todo!() - // let (message, is_continuation, is_last, is_admin) = self - // .active_chat - // .as_ref() - // .unwrap() - // .0 - // .update(cx, |active_chat, cx| { - // let is_admin = self - // .channel_store - // .read(cx) - // .is_channel_admin(active_chat.channel_id); + let active_chat = &self.active_chat.as_ref().unwrap().0; + let (message, is_continuation, is_admin) = active_chat.update(cx, |active_chat, cx| { + let is_admin = self + .channel_store + .read(cx) + .is_channel_admin(active_chat.channel_id); - // let last_message = active_chat.message(ix.saturating_sub(1)); - // let this_message = active_chat.message(ix).clone(); - // let is_continuation = last_message.id != this_message.id - // && this_message.sender.id == last_message.sender.id; + let last_message = active_chat.message(ix.saturating_sub(1)); + let this_message = active_chat.message(ix).clone(); + let is_continuation = last_message.id != this_message.id + && this_message.sender.id == last_message.sender.id; - // if let ChannelMessageId::Saved(id) = this_message.id { - // if this_message - // .mentions - // .iter() - // .any(|(_, user_id)| Some(*user_id) == self.client.user_id()) - // { - // active_chat.acknowledge_message(id); - // } - // } + if let ChannelMessageId::Saved(id) = this_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, - // active_chat.message_count() == ix + 1, - // is_admin, - // ) - // }); + (this_message, is_continuation, is_admin) + }); - // let is_pending = message.is_pending(); - // let text = self.markdown_data.entry(message.id).or_insert_with(|| { - // Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message) - // }); + let _is_pending = message.is_pending(); + let text = self.markdown_data.entry(message.id).or_insert_with(|| { + Self::render_markdown_with_mentions(&self.languages, self.client.id(), &message) + }); - // let now = OffsetDateTime::now_utc(); + let now = OffsetDateTime::now_utc(); - // let belongs_to_user = Some(message.sender.id) == self.client.user_id(); - // let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) = - // (message.id, belongs_to_user || is_admin) - // { - // Some(id) - // } else { - // None - // }; + let belongs_to_user = Some(message.sender.id) == self.client.user_id(); + let message_id_to_remove = if let (ChannelMessageId::Saved(id), true) = + (message.id, belongs_to_user || is_admin) + { + Some(id) + } else { + None + }; - // if is_continuation { - // h_stack() - // .child(text.element(cx)) - // .child(render_remove(message_id_to_remove, cx)) - // .mb_1() - // .into_any() - // } else { - // v_stack() - // .child( - // h_stack() - // .child(Avatar::data(message.sender.avatar.clone())) - // .child(Label::new(message.sender.github_login.clone())) - // .child( - // Label::new(format_timestamp( - // message.timestamp, - // now, - // self.local_timezone, - // )) - // .flex(1., true), - // ) - // .child(render_remove(message_id_to_remove, cx)) - // .align_children_center(), - // ) - // .child( - // h_stack() - // .child(text.element(cx)) - // .child(render_remove(None, cx)), - // ) - // .mb_1() - // .into_any() - // } + // todo!("render the text with markdown formatting") + if is_continuation { + h_stack() + .child(SharedString::from(text.text.clone())) + .child(render_remove(message_id_to_remove, cx)) + .mb_1() + .into_any() + } else { + v_stack() + .child( + h_stack() + .children( + message + .sender + .avatar + .clone() + .map(|avatar| Avatar::data(avatar)), + ) + .child(Label::new(message.sender.github_login.clone())) + .child(Label::new(format_timestamp( + message.timestamp, + now, + self.local_timezone, + ))) + .child(render_remove(message_id_to_remove, cx)), + ) + .child( + h_stack() + .child(SharedString::from(text.text.clone())) + .child(render_remove(None, cx)), + ) + .mb_1() + .into_any() + } } fn render_markdown_with_mentions( @@ -509,31 +522,25 @@ impl ChatPanel { // } fn render_sign_in_prompt(&self, cx: &mut ViewContext) -> AnyElement { - todo!() - // enum SignInPromptLabel {} - - // Button::new("sign-in", "Sign in to use chat") - // .on_click(move |_, this, cx| { - // let client = this.client.clone(); - // cx.spawn(|this, mut cx| async move { - // if client - // .authenticate_and_connect(true, &cx) - // .log_err() - // .await - // .is_some() - // { - // this.update(&mut cx, |this, cx| { - // if cx.handle().is_focused(cx) { - // cx.focus(&this.input_editor); - // } - // }) - // .ok(); - // } - // }) - // .detach(); - // }) - // .aligned() - // .into_any() + Button::new("sign-in", "Sign in to use chat") + .on_click(cx.listener(move |this, _, cx| { + let client = this.client.clone(); + cx.spawn(|this, mut cx| async move { + if client + .authenticate_and_connect(true, &cx) + .log_err() + .await + .is_some() + { + this.update(&mut cx, |_, cx| { + cx.focus_self(); + }) + .ok(); + } + }) + .detach(); + })) + .into_any_element() } fn send(&mut self, _: &Confirm, cx: &mut ViewContext) { @@ -557,7 +564,7 @@ impl ChatPanel { } } - fn load_more_messages(&mut self, _: &LoadMoreMessages, cx: &mut ViewContext) { + fn load_more_messages(&mut self, cx: &mut ViewContext) { if let Some((chat, _)) = self.active_chat.as_ref() { chat.update(cx, |channel, cx| { if let Some(task) = channel.load_more_messages(cx) { @@ -573,48 +580,47 @@ impl ChatPanel { scroll_to_message_id: Option, cx: &mut ViewContext, ) -> Task> { - todo!() - // let open_chat = self - // .active_chat - // .as_ref() - // .and_then(|(chat, _)| { - // (chat.read(cx).channel_id == selected_channel_id) - // .then(|| Task::ready(anyhow::Ok(chat.clone()))) - // }) - // .unwrap_or_else(|| { - // self.channel_store.update(cx, |store, cx| { - // store.open_channel_chat(selected_channel_id, cx) - // }) - // }); + let open_chat = self + .active_chat + .as_ref() + .and_then(|(chat, _)| { + (chat.read(cx).channel_id == selected_channel_id) + .then(|| Task::ready(anyhow::Ok(chat.clone()))) + }) + .unwrap_or_else(|| { + self.channel_store.update(cx, |store, cx| { + store.open_channel_chat(selected_channel_id, cx) + }) + }); - // cx.spawn(|this, mut cx| async move { - // let chat = open_chat.await?; - // this.update(&mut cx, |this, cx| { - // this.set_active_chat(chat.clone(), cx); - // })?; + cx.spawn(|this, mut cx| async move { + let chat = open_chat.await?; + this.update(&mut cx, |this, cx| { + this.set_active_chat(chat.clone(), cx); + })?; - // if let Some(message_id) = scroll_to_message_id { - // if let Some(item_ix) = - // ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone()) - // .await - // { - // this.update(&mut cx, |this, cx| { - // if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) { - // this.message_list.scroll_to(ListOffset { - // item_ix, - // offset_in_item: px(0.0), - // }); - // cx.notify(); - // } - // })?; - // } - // } + if let Some(message_id) = scroll_to_message_id { + if let Some(item_ix) = + ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone()) + .await + { + this.update(&mut cx, |this, cx| { + if this.active_chat.as_ref().map_or(false, |(c, _)| *c == chat) { + this.message_list.scroll_to(ListOffset { + item_ix, + offset_in_item: px(0.0), + }); + cx.notify(); + } + })?; + } + } - // Ok(()) - // }) + Ok(()) + }) } - fn open_notes(&mut self, _: &OpenChannelNotes, cx: &mut ViewContext) { + fn open_notes(&mut self, _: &ClickEvent, cx: &mut ViewContext) { if let Some((chat, _)) = &self.active_chat { let channel_id = chat.read(cx).channel_id; if let Some(workspace) = self.workspace.upgrade() { @@ -623,7 +629,7 @@ impl ChatPanel { } } - fn join_call(&mut self, _: &JoinCall, cx: &mut ViewContext) { + fn join_call(&mut self, _: &ClickEvent, cx: &mut ViewContext) { if let Some((chat, _)) = &self.active_chat { let channel_id = chat.read(cx).channel_id; ActiveCall::global(cx) @@ -634,40 +640,15 @@ impl ChatPanel { } fn render_remove(message_id_to_remove: Option, cx: &mut ViewContext) -> AnyElement { - todo!() - // enum DeleteMessage {} - - // 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() - // .into_any() - // }) - // .unwrap_or_else(|| { - // let style = theme.chat_panel.icon_button.default; - - // Empty::new() - // .constrained() - // .with_width(style.icon_width) - // .aligned() - // .constrained() - // .with_width(style.button_width) - // .with_height(style.button_width) - // .contained() - // .with_uniform_padding(2.) - // .flex_float() - // .into_any() - // }) + if let Some(message_id) = message_id_to_remove { + IconButton::new(("remove", message_id), Icon::XCircle) + .on_click(cx.listener(move |this, _, cx| { + this.remove_message(message_id, cx); + })) + .into_any_element() + } else { + div().into_any_element() + } } impl EventEmitter for ChatPanel {} @@ -677,6 +658,7 @@ impl Render for ChatPanel { fn render(&mut self, cx: &mut ViewContext) -> Self::Element { div() + .full() .child(if self.client.user_id().is_some() { self.render_channel(cx) } else { @@ -729,7 +711,7 @@ impl Panel for ChatPanel { } fn persistent_name() -> &'static str { - todo!() + "ChatPanel" } fn icon(&self, _cx: &WindowContext) -> Option { @@ -737,7 +719,7 @@ impl Panel for ChatPanel { } fn toggle_action(&self) -> Box { - todo!() + Box::new(ToggleFocus) } } diff --git a/crates/collab_ui2/src/chat_panel/message_editor.rs b/crates/collab_ui2/src/chat_panel/message_editor.rs index 6583818481..c28bcfa62e 100644 --- a/crates/collab_ui2/src/chat_panel/message_editor.rs +++ b/crates/collab_ui2/src/chat_panel/message_editor.rs @@ -3,7 +3,8 @@ use client::UserId; use collections::HashMap; use editor::{AnchorRangeExt, Editor}; use gpui::{ - AnyView, AsyncWindowContext, Model, Render, SharedString, Task, View, ViewContext, WeakView, + AnyView, AsyncWindowContext, FocusableView, Model, Render, SharedString, Task, View, + ViewContext, WeakView, }; use language::{language_settings::SoftWrap, Buffer, BufferSnapshot, LanguageRegistry}; use lazy_static::lazy_static; @@ -52,8 +53,7 @@ impl MessageEditor { let markdown = markdown.await?; buffer.update(&mut cx, |buffer, cx| { buffer.set_language(Some(markdown), cx) - }); - anyhow::Ok(()) + }) }) .detach_and_log_err(cx); @@ -191,14 +191,14 @@ impl MessageEditor { } pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle { - todo!() + self.editor.read(cx).focus_handle(cx) } } impl Render for MessageEditor { type Element = AnyView; - fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { self.editor.to_any() } } diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index ec6018cd68..9dcad01acd 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -192,6 +192,7 @@ use workspace::{ }; use crate::channel_view::ChannelView; +use crate::chat_panel::ChatPanel; use crate::{face_pile::FacePile, CollaborationPanelSettings}; use self::channel_modal::ChannelModal; @@ -2102,14 +2103,13 @@ impl CollabPanel { }; cx.window_context().defer(move |cx| { workspace.update(cx, |workspace, cx| { - todo!(); - // if let Some(panel) = workspace.focus_panel::(cx) { - // panel.update(cx, |panel, cx| { - // panel - // .select_channel(channel_id, None, cx) - // .detach_and_log_err(cx); - // }); - // } + if let Some(panel) = workspace.focus_panel::(cx) { + panel.update(cx, |panel, cx| { + panel + .select_channel(channel_id, None, cx) + .detach_and_log_err(cx); + }); + } }); }); } @@ -2603,9 +2603,14 @@ impl CollabPanel { Color::Default } else { Color::Muted + }) + .on_click(cx.listener(move |this, _, cx| { + this.join_channel_chat(channel_id, cx) + })) + .tooltip(|cx| { + Tooltip::text("Open channel chat", cx) }), - ) - .tooltip(|cx| Tooltip::text("Open channel chat", cx)), + ), ) .child( div() diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index ab26c6f485..a2dcb6ed61 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -132,7 +132,7 @@ impl ListState { } pub fn set_scroll_handler( - &mut self, + &self, handler: impl FnMut(&ListScrollEvent, &mut WindowContext) + 'static, ) { self.0.borrow_mut().scroll_handler = Some(Box::new(handler)) diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index d198b0485e..c0cbd127ac 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -1,12 +1,10 @@ -use std::any::TypeId; - use crate::{ItemHandle, Pane}; use gpui::{ div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WindowContext, }; -use ui::h_stack; -use ui::prelude::*; +use std::any::TypeId; +use ui::{h_stack, prelude::*}; use util::ResultExt; pub trait StatusItemView: Render { @@ -47,8 +45,8 @@ impl Render for StatusBar { .w_full() .h_8() .bg(cx.theme().colors().status_bar_background) - .child(h_stack().gap_1().child(self.render_left_tools(cx))) - .child(h_stack().gap_4().child(self.render_right_tools(cx))) + .child(self.render_left_tools(cx)) + .child(self.render_right_tools(cx)) } } diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index d36d16a654..0af5777f17 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -160,8 +160,8 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone()); let channels_panel = collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone()); - // let chat_panel = - // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone()); + let chat_panel = + collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone()); // let notification_panel = collab_ui::notification_panel::NotificationPanel::load( // workspace_handle.clone(), // cx.clone(), @@ -171,14 +171,14 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { terminal_panel, assistant_panel, channels_panel, - // chat_panel, + chat_panel, // notification_panel, ) = futures::try_join!( project_panel, terminal_panel, assistant_panel, channels_panel, - // chat_panel, + chat_panel, // notification_panel, )?; @@ -188,7 +188,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { workspace.add_panel(terminal_panel, cx); workspace.add_panel(assistant_panel, cx); workspace.add_panel(channels_panel, cx); - // workspace.add_panel(chat_panel, cx); + workspace.add_panel(chat_panel, cx); // workspace.add_panel(notification_panel, cx); // if !was_deserialized