Tune UX for context menus

Mikayla and max created

Co-authored-by: max <max@zed.dev>

Change summary

crates/client/src/user.rs                          | 16 ++++
crates/collab_ui/src/collab_panel.rs               | 45 +++++++++++----
crates/collab_ui/src/collab_panel/channel_modal.rs | 16 +++--
3 files changed, 56 insertions(+), 21 deletions(-)

Detailed changes

crates/client/src/user.rs 🔗

@@ -165,17 +165,29 @@ impl UserStore {
                                 });
 
                                 current_user_tx.send(user).await.ok();
+
+                                this.update(&mut cx, |_, cx| {
+                                    cx.notify();
+                                });
                             }
                         }
                         Status::SignedOut => {
                             current_user_tx.send(None).await.ok();
                             if let Some(this) = this.upgrade(&cx) {
-                                this.update(&mut cx, |this, _| this.clear_contacts()).await;
+                                this.update(&mut cx, |this, cx| {
+                                    cx.notify();
+                                    this.clear_contacts()
+                                })
+                                .await;
                             }
                         }
                         Status::ConnectionLost => {
                             if let Some(this) = this.upgrade(&cx) {
-                                this.update(&mut cx, |this, _| this.clear_contacts()).await;
+                                this.update(&mut cx, |this, cx| {
+                                    cx.notify();
+                                    this.clear_contacts()
+                                })
+                                .await;
                             }
                         }
                         _ => {}

crates/collab_ui/src/collab_panel.rs 🔗

@@ -4,7 +4,7 @@ mod panel_settings;
 
 use anyhow::Result;
 use call::ActiveCall;
-use client::{proto::PeerId, Channel, ChannelStore, Client, Contact, User, UserStore};
+use client::{proto::PeerId, Channel, ChannelId, ChannelStore, Client, Contact, User, UserStore};
 use contact_finder::build_contact_finder;
 use context_menu::{ContextMenu, ContextMenuItem};
 use db::kvp::KEY_VALUE_STORE;
@@ -55,13 +55,21 @@ struct NewChannel {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct AddMember {
+struct InviteMembers {
+    channel_id: u64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+struct ManageMembers {
     channel_id: u64,
 }
 
 actions!(collab_panel, [ToggleFocus]);
 
-impl_actions!(collab_panel, [RemoveChannel, NewChannel, AddMember]);
+impl_actions!(
+    collab_panel,
+    [RemoveChannel, NewChannel, InviteMembers, ManageMembers]
+);
 
 const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel";
 
@@ -76,7 +84,8 @@ pub fn init(_client: Arc<Client>, cx: &mut AppContext) {
     cx.add_action(CollabPanel::confirm);
     cx.add_action(CollabPanel::remove_channel);
     cx.add_action(CollabPanel::new_subchannel);
-    cx.add_action(CollabPanel::add_member);
+    cx.add_action(CollabPanel::invite_members);
+    cx.add_action(CollabPanel::manage_members);
 }
 
 #[derive(Debug, Default)]
@@ -325,6 +334,7 @@ impl CollabPanel {
                 client: workspace.app_state().client.clone(),
                 list_state,
             };
+
             this.update_entries(cx);
 
             // Update the dock position when the setting changes.
@@ -1549,7 +1559,8 @@ impl CollabPanel {
                     vec![
                         ContextMenuItem::action("New Channel", NewChannel { channel_id }),
                         ContextMenuItem::action("Remove Channel", RemoveChannel { channel_id }),
-                        ContextMenuItem::action("Add member", AddMember { channel_id }),
+                        ContextMenuItem::action("Manage members", ManageMembers { channel_id }),
+                        ContextMenuItem::action("Invite members", InviteMembers { channel_id }),
                     ],
                     cx,
                 );
@@ -1710,8 +1721,20 @@ impl CollabPanel {
         cx.notify();
     }
 
-    fn add_member(&mut self, action: &AddMember, cx: &mut ViewContext<Self>) {
-        let channel_id = action.channel_id;
+    fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
+        self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
+    }
+
+    fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
+        self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
+    }
+
+    fn show_channel_modal(
+        &mut self,
+        channel_id: ChannelId,
+        mode: channel_modal::Mode,
+        cx: &mut ViewContext<Self>,
+    ) {
         let workspace = self.workspace.clone();
         let user_store = self.user_store.clone();
         let channel_store = self.channel_store.clone();
@@ -1728,7 +1751,7 @@ impl CollabPanel {
                             user_store.clone(),
                             channel_store.clone(),
                             channel_id,
-                            channel_modal::Mode::InviteMembers,
+                            mode,
                             members,
                             cx,
                         )
@@ -1879,12 +1902,8 @@ impl View for CollabPanel {
                     })
                     .on_click(MouseButton::Left, |_, this, cx| {
                         let client = this.client.clone();
-                        cx.spawn(|this, mut cx| async move {
+                        cx.spawn(|_, cx| async move {
                             client.authenticate_and_connect(true, &cx).await.log_err();
-
-                            this.update(&mut cx, |_, cx| {
-                                cx.notify();
-                            })
                         })
                         .detach();
                     })

crates/collab_ui/src/collab_panel/channel_modal.rs 🔗

@@ -337,7 +337,7 @@ impl PickerDelegate for ChannelModalDelegate {
                 Mode::ManageMembers => self.show_context_menu(admin.unwrap_or(false), cx),
                 Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
                     Some(proto::channel_member::Kind::Invitee) => {
-                        self.remove_member(selected_user.id, cx);
+                        self.remove_selected_member(cx);
                     }
                     Some(proto::channel_member::Kind::AncestorMember) | None => {
                         self.invite_member(selected_user, cx)
@@ -502,6 +502,7 @@ impl ChannelModalDelegate {
                 if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user.id) {
                     member.admin = admin;
                 }
+                cx.focus_self();
                 cx.notify();
             })
         })
@@ -511,11 +512,7 @@ impl ChannelModalDelegate {
 
     fn remove_selected_member(&mut self, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
         let (user, _) = self.user_at_index(self.selected_index)?;
-        self.remove_member(user.id, cx);
-        Some(())
-    }
-
-    fn remove_member(&mut self, user_id: u64, cx: &mut ViewContext<Picker<Self>>) {
+        let user_id = user.id;
         let update = self.channel_store.update(cx, |store, cx| {
             store.remove_member(self.channel_id, user_id, cx)
         });
@@ -534,10 +531,17 @@ impl ChannelModalDelegate {
                         true
                     })
                 }
+
+                this.selected_index = this
+                    .selected_index
+                    .min(this.matching_member_indices.len() - 1);
+
+                cx.focus_self();
                 cx.notify();
             })
         })
         .detach_and_log_err(cx);
+        Some(())
     }
 
     fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {