Add buttons for opening channel notes and joining call, in chat panel header

Max Brunsfeld created

Change summary

crates/collab_ui/src/channel_view.rs | 25 +++++++++
crates/collab_ui/src/chat_panel.rs   | 82 +++++++++++++++++++++++------
crates/collab_ui/src/collab_panel.rs | 26 ---------
crates/gpui/src/views/select.rs      |  6 +
crates/theme/src/theme.rs            |  2 
styles/src/style_tree/chat_panel.ts  | 10 ++-
6 files changed, 100 insertions(+), 51 deletions(-)

Detailed changes

crates/collab_ui/src/channel_view.rs 🔗

@@ -1,4 +1,5 @@
 use anyhow::{anyhow, Result};
+use call::ActiveCall;
 use channel::{ChannelBuffer, ChannelBufferEvent, ChannelId};
 use client::proto;
 use clock::ReplicaId;
@@ -35,6 +36,30 @@ pub struct ChannelView {
 }
 
 impl ChannelView {
+    pub fn deploy(channel_id: ChannelId, workspace: ViewHandle<Workspace>, cx: &mut AppContext) {
+        let pane = workspace.read(cx).active_pane().clone();
+        let channel_view = Self::open(channel_id, pane.clone(), workspace.clone(), cx);
+        cx.spawn(|mut cx| async move {
+            let channel_view = channel_view.await?;
+            pane.update(&mut cx, |pane, cx| {
+                let room_id = ActiveCall::global(cx)
+                    .read(cx)
+                    .room()
+                    .map(|room| room.read(cx).id());
+                ActiveCall::report_call_event_for_room(
+                    "open channel notes",
+                    room_id,
+                    Some(channel_id),
+                    &workspace.read(cx).app_state().client,
+                    cx,
+                );
+                pane.add_item(Box::new(channel_view), true, true, None, cx);
+            });
+            anyhow::Ok(())
+        })
+        .detach();
+    }
+
     pub fn open(
         channel_id: ChannelId,
         pane: ViewHandle<Pane>,

crates/collab_ui/src/chat_panel.rs 🔗

@@ -1,5 +1,6 @@
-use crate::ChatPanelSettings;
+use crate::{channel_view::ChannelView, ChatPanelSettings};
 use anyhow::Result;
+use call::ActiveCall;
 use channel::{ChannelChat, ChannelChatEvent, ChannelMessageId, ChannelStore};
 use client::Client;
 use db::kvp::KEY_VALUE_STORE;
@@ -80,8 +81,11 @@ impl ChatPanel {
             editor
         });
 
+        let workspace_handle = workspace.weak_handle();
+
         let channel_select = cx.add_view(|cx| {
             let channel_store = channel_store.clone();
+            let workspace = workspace_handle.clone();
             Select::new(0, cx, {
                 move |ix, item_type, is_hovered, cx| {
                     Self::render_channel_name(
@@ -89,7 +93,8 @@ impl ChatPanel {
                         ix,
                         item_type,
                         is_hovered,
-                        &theme::current(cx).chat_panel.channel_select,
+                        &theme::current(cx).chat_panel,
+                        workspace,
                         cx,
                     )
                 }
@@ -334,7 +339,7 @@ impl ChatPanel {
                             cx,
                             |mouse_state, _| {
                                 let button_style =
-                                    theme.collab_panel.contact_button.style_for(mouse_state);
+                                    theme.chat_panel.icon_button.style_for(mouse_state);
                                 render_icon_button(button_style, "icons/x.svg")
                                     .aligned()
                                     .into_any()
@@ -366,28 +371,62 @@ impl ChatPanel {
         ix: usize,
         item_type: ItemType,
         is_hovered: bool,
-        theme: &theme::ChannelSelect,
-        cx: &AppContext,
+        theme: &theme::ChatPanel,
+        workspace: WeakViewHandle<Workspace>,
+        cx: &mut ViewContext<Select>,
     ) -> AnyElement<Select> {
+        enum ChannelNotes {}
+        enum JoinCall {}
+
         let channel = &channel_store.read(cx).channel_at_index(ix).unwrap().1;
-        let theme = match (item_type, is_hovered) {
-            (ItemType::Header, _) => &theme.header,
-            (ItemType::Selected, false) => &theme.active_item,
-            (ItemType::Selected, true) => &theme.hovered_active_item,
-            (ItemType::Unselected, false) => &theme.item,
-            (ItemType::Unselected, true) => &theme.hovered_item,
+        let channel_id = channel.id;
+        let style = &theme.channel_select;
+        let style = match (&item_type, is_hovered) {
+            (ItemType::Header, _) => &style.header,
+            (ItemType::Selected, _) => &style.active_item,
+            (ItemType::Unselected, false) => &style.item,
+            (ItemType::Unselected, true) => &style.hovered_item,
         };
-        Flex::row()
+        let mut row = Flex::row()
             .with_child(
-                Label::new("#".to_string(), theme.hash.text.clone())
+                Label::new("#".to_string(), style.hash.text.clone())
                     .contained()
-                    .with_style(theme.hash.container),
+                    .with_style(style.hash.container),
             )
-            .with_child(Label::new(channel.name.clone(), theme.name.clone()))
-            .aligned()
-            .left()
+            .with_child(Label::new(channel.name.clone(), style.name.clone()));
+
+        if matches!(item_type, ItemType::Header) {
+            row.add_children([
+                MouseEventHandler::new::<ChannelNotes, _>(0, cx, |mouse_state, _| {
+                    render_icon_button(
+                        theme.icon_button.style_for(mouse_state),
+                        "icons/radix/file.svg",
+                    )
+                })
+                .on_click(MouseButton::Left, move |_, _, cx| {
+                    if let Some(workspace) = workspace.upgrade(cx) {
+                        ChannelView::deploy(channel_id, workspace, cx);
+                    }
+                })
+                .flex_float(),
+                MouseEventHandler::new::<JoinCall, _>(0, cx, |mouse_state, _| {
+                    render_icon_button(
+                        theme.icon_button.style_for(mouse_state),
+                        "icons/radix/speaker-loud.svg",
+                    )
+                })
+                .on_click(MouseButton::Left, move |_, _, cx| {
+                    ActiveCall::global(cx)
+                        .update(cx, |call, cx| call.join_channel(channel_id, cx))
+                        .detach_and_log_err(cx);
+                })
+                .flex_float(),
+            ]);
+        }
+
+        row.align_children_center()
             .contained()
-            .with_style(theme.container)
+            .with_style(style.container)
             .into_any()
     }
 
@@ -511,6 +550,7 @@ impl View for ChatPanel {
     }
 
     fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+        self.has_focus = true;
         if matches!(
             *self.client.status().borrow(),
             client::Status::Connected { .. }
@@ -518,6 +558,10 @@ impl View for ChatPanel {
             cx.focus(&self.input_editor);
         }
     }
+
+    fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
+        self.has_focus = false;
+    }
 }
 
 impl Panel for ChatPanel {
@@ -594,7 +638,7 @@ fn format_timestamp(
     }
 }
 
-fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element<ChatPanel> {
+fn render_icon_button<V: View>(style: &IconButton, svg_path: &'static str) -> impl Element<V> {
     Svg::new(svg_path)
         .with_color(style.color)
         .constrained()

crates/collab_ui/src/collab_panel.rs 🔗

@@ -2252,29 +2252,7 @@ impl CollabPanel {
 
     fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
         if let Some(workspace) = self.workspace.upgrade(cx) {
-            let pane = workspace.read(cx).active_pane().clone();
-            let channel_id = action.channel_id;
-            let channel_view = ChannelView::open(channel_id, pane.clone(), workspace, cx);
-            cx.spawn(|_, mut cx| async move {
-                let channel_view = channel_view.await?;
-                pane.update(&mut cx, |pane, cx| {
-                    pane.add_item(Box::new(channel_view), true, true, None, cx)
-                });
-                anyhow::Ok(())
-            })
-            .detach();
-            let room_id = ActiveCall::global(cx)
-                .read(cx)
-                .room()
-                .map(|room| room.read(cx).id());
-
-            ActiveCall::report_call_event_for_room(
-                "open channel notes",
-                room_id,
-                Some(channel_id),
-                &self.client,
-                cx,
-            );
+            ChannelView::deploy(action.channel_id, workspace, cx);
         }
     }
 
@@ -2436,8 +2414,6 @@ impl CollabPanel {
     }
 
     fn join_channel_chat(&mut self, channel_id: u64, cx: &mut ViewContext<Self>) {
-        self.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-
         if let Some(workspace) = self.workspace.upgrade(cx) {
             cx.app_context().defer(move |cx| {
                 workspace.update(cx, |workspace, cx| {

crates/gpui/src/views/select.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
 
 pub struct Select {
     handle: WeakViewHandle<Self>,
-    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>,
+    render_item: Box<dyn Fn(usize, ItemType, bool, &mut ViewContext<Select>) -> AnyElement<Self>>,
     selected_item_ix: usize,
     item_count: usize,
     is_open: bool,
@@ -29,7 +29,9 @@ pub enum ItemType {
 pub enum Event {}
 
 impl Select {
-    pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>(
+    pub fn new<
+        F: 'static + Fn(usize, ItemType, bool, &mut ViewContext<Self>) -> AnyElement<Self>,
+    >(
         item_count: usize,
         cx: &mut ViewContext<Self>,
         render_item: F,

crates/theme/src/theme.rs 🔗

@@ -635,6 +635,7 @@ pub struct ChatPanel {
     pub message: ChatMessage,
     pub pending_message: ChatMessage,
     pub sign_in_prompt: Interactive<TextStyle>,
+    pub icon_button: Interactive<IconButton>,
 }
 
 #[derive(Deserialize, Default, JsonSchema)]
@@ -654,7 +655,6 @@ pub struct ChannelSelect {
     pub item: ChannelName,
     pub active_item: ChannelName,
     pub hovered_item: ChannelName,
-    pub hovered_active_item: ChannelName,
     pub menu: ContainerStyle,
 }
 

styles/src/style_tree/chat_panel.ts 🔗

@@ -3,6 +3,7 @@ import {
     border,
     text,
 } from "./components"
+import { icon_button } from "../component/icon_button"
 import { useTheme } from "../theme"
 
 export default function chat_panel(): any {
@@ -46,15 +47,16 @@ export default function chat_panel(): any {
                 ...channel_name,
                 background: background(layer, "on", "hovered"),
             },
-            hovered_active_item: {
-                ...channel_name,
-                background: background(layer, "on", "active"),
-            },
             menu: {
                 background: background(layer, "on"),
                 border: border(layer, { bottom: true })
             }
         },
+        icon_button: icon_button({
+            variant: "ghost",
+            color: "variant",
+            size: "sm",
+        }),
         input_editor: {
             background: background(layer, "on"),
             corner_radius: 6,