Remove admin and member button

Mikayla and Max created

Fix bug with invites in the member list
Fix bug when there are network errors in the member related RPC calls

co-authored-by: Max <max@zed.dev>

Change summary

crates/client/src/channel_store.rs                 |  21 ++
crates/collab_ui/src/collab_panel/channel_modal.rs | 119 +++++++--------
crates/theme/src/theme.rs                          |   3 
styles/src/style_tree/channel_modal.ts             |  15 -
4 files changed, 76 insertions(+), 82 deletions(-)

Detailed changes

crates/client/src/channel_store.rs 🔗

@@ -127,17 +127,21 @@ impl ChannelStore {
         cx.notify();
         let client = self.client.clone();
         cx.spawn(|this, mut cx| async move {
-            client
+            let result = client
                 .request(proto::InviteChannelMember {
                     channel_id,
                     user_id,
                     admin,
                 })
-                .await?;
+                .await;
+
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
             });
+
+            result?;
+
             Ok(())
         })
     }
@@ -155,16 +159,18 @@ impl ChannelStore {
         cx.notify();
         let client = self.client.clone();
         cx.spawn(|this, mut cx| async move {
-            client
+            let result = client
                 .request(proto::RemoveChannelMember {
                     channel_id,
                     user_id,
                 })
-                .await?;
+                .await;
+
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
             });
+            result?;
             Ok(())
         })
     }
@@ -183,17 +189,20 @@ impl ChannelStore {
         cx.notify();
         let client = self.client.clone();
         cx.spawn(|this, mut cx| async move {
-            client
+            let result = client
                 .request(proto::SetChannelMemberAdmin {
                     channel_id,
                     user_id,
                     admin,
                 })
-                .await?;
+                .await;
+
             this.update(&mut cx, |this, cx| {
                 this.outgoing_invites.remove(&(channel_id, user_id));
                 cx.notify();
             });
+
+            result?;
             Ok(())
         })
     }

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

@@ -45,15 +45,7 @@ impl ChannelModal {
                     user_store: user_store.clone(),
                     channel_store: channel_store.clone(),
                     channel_id,
-                    match_candidates: members
-                        .iter()
-                        .enumerate()
-                        .map(|(id, member)| StringMatchCandidate {
-                            id,
-                            string: member.user.github_login.clone(),
-                            char_bag: member.user.github_login.chars().collect(),
-                        })
-                        .collect(),
+                    match_candidates: Vec::new(),
                     members,
                     mode,
                     selected_column: None,
@@ -256,7 +248,7 @@ pub struct ChannelModalDelegate {
     selected_index: usize,
     mode: Mode,
     selected_column: Option<UserColumn>,
-    match_candidates: Arc<[StringMatchCandidate]>,
+    match_candidates: Vec<StringMatchCandidate>,
     members: Vec<ChannelMembership>,
 }
 
@@ -287,30 +279,36 @@ impl PickerDelegate for ChannelModalDelegate {
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
         match self.mode {
             Mode::ManageMembers => {
-                let match_candidates = self.match_candidates.clone();
+                self.match_candidates.clear();
+                self.match_candidates
+                    .extend(self.members.iter().enumerate().map(|(id, member)| {
+                        StringMatchCandidate {
+                            id,
+                            string: member.user.github_login.clone(),
+                            char_bag: member.user.github_login.chars().collect(),
+                        }
+                    }));
+
+                let matches = cx.background().block(match_strings(
+                    &self.match_candidates,
+                    &query,
+                    true,
+                    usize::MAX,
+                    &Default::default(),
+                    cx.background().clone(),
+                ));
+
                 cx.spawn(|picker, mut cx| async move {
-                    async move {
-                        let matches = match_strings(
-                            &match_candidates,
-                            &query,
-                            true,
-                            usize::MAX,
-                            &Default::default(),
-                            cx.background().clone(),
-                        )
-                        .await;
-                        picker.update(&mut cx, |picker, cx| {
+                    picker
+                        .update(&mut cx, |picker, cx| {
                             let delegate = picker.delegate_mut();
                             delegate.matching_member_indices.clear();
                             delegate
                                 .matching_member_indices
                                 .extend(matches.into_iter().map(|m| m.candidate_id));
                             cx.notify();
-                        })?;
-                        anyhow::Ok(())
-                    }
-                    .log_err()
-                    .await;
+                        })
+                        .ok();
                 })
             }
             Mode::InviteMembers => {
@@ -346,11 +344,7 @@ impl PickerDelegate for ChannelModalDelegate {
                     }
                 }
                 Some(proto::channel_member::Kind::AncestorMember) | None => {
-                    self.channel_store
-                        .update(cx, |store, cx| {
-                            store.invite_member(self.channel_id, selected_user.id, false, cx)
-                        })
-                        .detach();
+                    self.invite_member(selected_user, cx)
                 }
             }
         }
@@ -386,41 +380,24 @@ impl PickerDelegate for ChannelModalDelegate {
                     .aligned()
                     .left(),
             )
-            .with_children(admin.map(|admin| {
-                let member_style = theme.admin_toggle_part.in_state(!admin);
-                let admin_style = theme.admin_toggle_part.in_state(admin);
-                Flex::row()
-                    .with_child(
-                        Label::new("member", member_style.text.clone())
-                            .contained()
-                            .with_style(member_style.container),
-                    )
-                    .with_child(
-                        Label::new("admin", admin_style.text.clone())
-                            .contained()
-                            .with_style(admin_style.container),
-                    )
+            .with_children(admin.map(|_| {
+                Label::new("admin", theme.admin_toggle.text.clone())
                     .contained()
-                    .with_style(theme.admin_toggle)
+                    .with_style(theme.admin_toggle.container)
                     .aligned()
-                    .flex_float()
             }))
             .with_children({
                 match self.mode {
                     Mode::ManageMembers => match request_status {
-                        Some(proto::channel_member::Kind::Member) => Some(
-                            Label::new("remove member", theme.remove_member_button.text.clone())
-                                .contained()
-                                .with_style(theme.remove_member_button.container)
-                                .into_any(),
-                        ),
                         Some(proto::channel_member::Kind::Invitee) => Some(
                             Label::new("cancel invite", theme.cancel_invite_button.text.clone())
                                 .contained()
                                 .with_style(theme.cancel_invite_button.container)
                                 .into_any(),
                         ),
-                        Some(proto::channel_member::Kind::AncestorMember) | None => None,
+                        Some(proto::channel_member::Kind::Member)
+                        | Some(proto::channel_member::Kind::AncestorMember)
+                        | None => None,
                     },
                     Mode::InviteMembers => {
                         let svg = match request_status {
@@ -466,11 +443,12 @@ impl ChannelModalDelegate {
         self.members
             .iter()
             .find_map(|membership| (membership.user.id == user_id).then_some(membership.kind))
-            .or(self
-                .channel_store
-                .read(cx)
-                .has_pending_channel_invite(self.channel_id, user_id)
-                .then_some(proto::channel_member::Kind::Invitee))
+            .or_else(|| {
+                self.channel_store
+                    .read(cx)
+                    .has_pending_channel_invite(self.channel_id, user_id)
+                    .then_some(proto::channel_member::Kind::Invitee)
+            })
     }
 
     fn user_at_index(&self, ix: usize) -> Option<(Arc<User>, Option<bool>)> {
@@ -517,4 +495,25 @@ impl ChannelModalDelegate {
         })
         .detach_and_log_err(cx);
     }
+
+    fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
+        let invite_member = self.channel_store.update(cx, |store, cx| {
+            store.invite_member(self.channel_id, user.id, false, cx)
+        });
+
+        cx.spawn(|this, mut cx| async move {
+            invite_member.await?;
+
+            this.update(&mut cx, |this, cx| {
+                let delegate_mut = this.delegate_mut();
+                delegate_mut.members.push(ChannelMembership {
+                    user,
+                    kind: proto::channel_member::Kind::Invitee,
+                    admin: false,
+                });
+                cx.notify();
+            })
+        })
+        .detach_and_log_err(cx);
+    }
 }

crates/theme/src/theme.rs 🔗

@@ -261,8 +261,7 @@ pub struct ChannelModal {
     pub cancel_invite_button: ContainedText,
     pub member_icon: Icon,
     pub invitee_icon: Icon,
-    pub admin_toggle: ContainerStyle,
-    pub admin_toggle_part: Toggleable<ContainedText>,
+    pub admin_toggle: ContainedText,
 }
 
 #[derive(Deserialize, Default, JsonSchema)]

styles/src/style_tree/channel_modal.ts 🔗

@@ -76,21 +76,8 @@ export default function channel_modal(): any {
             ...text(theme.middle, "sans", { size: "xs" }),
             background: background(theme.middle),
         },
-        admin_toggle_part: toggleable({
-            base: {
-                ...text(theme.middle, "sans", { size: "xs" }),
-                padding: {
-                    left: 7,
-                    right: 7,
-                },
-            },
-            state: {
-                active: {
-                    background: background(theme.middle, "on"),
-                }
-            }
-        }),
         admin_toggle: {
+            ...text(theme.middle, "sans", { size: "xs" }),
             border: border(theme.middle, "active"),
             background: background(theme.middle),
             margin: {