Update Channel membership UI

Conrad Irwin created

* Remove ability to act on people defined in parent channels
* Show promote buttons on guests

Change summary

crates/collab_ui/src/collab_panel/channel_modal.rs | 128 +++++++++------
1 file changed, 79 insertions(+), 49 deletions(-)

Detailed changes

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

@@ -10,10 +10,11 @@ use gpui::{
     WeakView,
 };
 use picker::{Picker, PickerDelegate};
+use rpc::proto::channel_member;
 use std::sync::Arc;
 use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
 use util::TryFutureExt;
-use workspace::ModalView;
+use workspace::{notifications::NotifyTaskExt, ModalView};
 
 actions!(
     channel_modal,
@@ -347,15 +348,13 @@ impl PickerDelegate for ChannelModalDelegate {
     }
 
     fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
-        if let Some((selected_user, role)) = self.user_at_index(self.selected_index) {
+        if let Some(selected_user) = self.user_at_index(self.selected_index) {
             if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id)
             {
                 return;
             }
             match self.mode {
-                Mode::ManageMembers => {
-                    self.show_context_menu(selected_user, role.unwrap_or(ChannelRole::Member), cx)
-                }
+                Mode::ManageMembers => self.show_context_menu(self.selected_index, cx),
                 Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
                     Some(proto::channel_member::Kind::Invitee) => {
                         self.remove_member(selected_user.id, cx);
@@ -385,7 +384,8 @@ impl PickerDelegate for ChannelModalDelegate {
         selected: bool,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> Option<Self::ListItem> {
-        let (user, role) = self.user_at_index(ix)?;
+        let user = self.user_at_index(ix)?;
+        let membership = self.member_at_index(ix);
         let request_status = self.member_status(user.id, cx);
         let is_me = self.user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
 
@@ -402,11 +402,15 @@ impl PickerDelegate for ChannelModalDelegate {
                             .children(
                                 if request_status == Some(proto::channel_member::Kind::Invitee) {
                                     Some(Label::new("Invited"))
+                                } else if membership.map(|m| m.kind)
+                                    == Some(channel_member::Kind::AncestorMember)
+                                {
+                                    Some(Label::new("Parent"))
                                 } else {
                                     None
                                 },
                             )
-                            .children(match role {
+                            .children(match membership.map(|m| m.role) {
                                 Some(ChannelRole::Admin) => Some(Label::new("Admin")),
                                 Some(ChannelRole::Guest) => Some(Label::new("Guest")),
                                 _ => None,
@@ -419,7 +423,7 @@ impl PickerDelegate for ChannelModalDelegate {
                                 if let (Some((menu, _)), true) = (&self.context_menu, selected) {
                                     Some(
                                         overlay()
-                                            .anchor(gpui::AnchorCorner::TopLeft)
+                                            .anchor(gpui::AnchorCorner::TopRight)
                                             .child(menu.clone()),
                                     )
                                 } else {
@@ -458,16 +462,19 @@ impl ChannelModalDelegate {
             })
     }
 
-    fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<ChannelRole>)> {
+    fn member_at_index(&self, ix: usize) -> Option<&ChannelMembership> {
+        self.matching_member_indices
+            .get(ix)
+            .and_then(|ix| self.members.get(*ix))
+    }
+
+    fn user_at_index(&self, ix: usize) -> Option<Arc<User>> {
         match self.mode {
             Mode::ManageMembers => self.matching_member_indices.get(ix).and_then(|ix| {
                 let channel_membership = self.members.get(*ix)?;
-                Some((
-                    channel_membership.user.clone(),
-                    Some(channel_membership.role),
-                ))
+                Some(channel_membership.user.clone())
             }),
-            Mode::InviteMembers => Some((self.matching_users.get(ix).cloned()?, None)),
+            Mode::InviteMembers => self.matching_users.get(ix).cloned(),
         }
     }
 
@@ -491,7 +498,7 @@ impl ChannelModalDelegate {
                 cx.notify();
             })
         })
-        .detach_and_log_err(cx);
+        .detach_and_notify_err(cx);
         Some(())
     }
 
@@ -523,7 +530,7 @@ impl ChannelModalDelegate {
                 cx.notify();
             })
         })
-        .detach_and_log_err(cx);
+        .detach_and_notify_err(cx);
         Some(())
     }
 
@@ -549,19 +556,66 @@ impl ChannelModalDelegate {
                 cx.notify();
             })
         })
-        .detach_and_log_err(cx);
+        .detach_and_notify_err(cx);
     }
 
-    fn show_context_menu(
-        &mut self,
-        user: Arc<User>,
-        role: ChannelRole,
-        cx: &mut ViewContext<Picker<Self>>,
-    ) {
-        let user_id = user.id;
+    fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
+        let Some(membership) = self.member_at_index(ix) else {
+            return;
+        };
+        if membership.kind == proto::channel_member::Kind::AncestorMember {
+            return;
+        }
+        let user_id = membership.user.id;
         let picker = cx.view().clone();
         let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
-            menu = menu.entry("Remove Member", None, {
+            if membership.kind == channel_member::Kind::AncestorMember {
+                return menu.entry("Inherited membership", None, |_| {});
+            };
+
+            let role = membership.role;
+
+            if role == ChannelRole::Admin || role == ChannelRole::Member {
+                let picker = picker.clone();
+                menu = menu.entry("Demote to Guest", None, move |cx| {
+                    picker.update(cx, |picker, cx| {
+                        picker
+                            .delegate
+                            .set_user_role(user_id, ChannelRole::Guest, cx);
+                    })
+                });
+            }
+
+            if role == ChannelRole::Admin || role == ChannelRole::Guest {
+                let picker = picker.clone();
+                let label = if role == ChannelRole::Guest {
+                    "Promote to Member"
+                } else {
+                    "Demote to Member"
+                };
+
+                menu = menu.entry(label, None, move |cx| {
+                    picker.update(cx, |picker, cx| {
+                        picker
+                            .delegate
+                            .set_user_role(user_id, ChannelRole::Member, cx);
+                    })
+                });
+            }
+
+            if role == ChannelRole::Member || role == ChannelRole::Guest {
+                let picker = picker.clone();
+                menu = menu.entry("Promote to Admin", None, move |cx| {
+                    picker.update(cx, |picker, cx| {
+                        picker
+                            .delegate
+                            .set_user_role(user_id, ChannelRole::Admin, cx);
+                    })
+                });
+            };
+
+            menu = menu.separator();
+            menu = menu.entry("Remove from Channel", None, {
                 let picker = picker.clone();
                 move |cx| {
                     picker.update(cx, |picker, cx| {
@@ -569,30 +623,6 @@ impl ChannelModalDelegate {
                     })
                 }
             });
-
-            let picker = picker.clone();
-            match role {
-                ChannelRole::Admin => {
-                    menu = menu.entry("Revoke Admin", None, move |cx| {
-                        picker.update(cx, |picker, cx| {
-                            picker
-                                .delegate
-                                .set_user_role(user_id, ChannelRole::Member, cx);
-                        })
-                    });
-                }
-                ChannelRole::Member => {
-                    menu = menu.entry("Make Admin", None, move |cx| {
-                        picker.update(cx, |picker, cx| {
-                            picker
-                                .delegate
-                                .set_user_role(user_id, ChannelRole::Admin, cx);
-                        })
-                    });
-                }
-                _ => {}
-            };
-
             menu
         });
         cx.focus_view(&context_menu);