Show guests in fewer places

Conrad Irwin created

Change summary

crates/call/src/participant.rs                 |  1 
crates/call/src/room.rs                        | 19 +++-
crates/collab/src/tests/channel_guest_tests.rs |  8 -
crates/collab_ui/src/channel_view.rs           |  2 
crates/collab_ui/src/collab_panel.rs           | 77 +++++++++++++++++--
crates/collab_ui/src/collab_titlebar_item.rs   | 15 ++-
crates/theme/src/styles/players.rs             |  4 
7 files changed, 100 insertions(+), 26 deletions(-)

Detailed changes

crates/call/src/participant.rs 🔗

@@ -43,6 +43,7 @@ pub struct LocalParticipant {
 pub struct RemoteParticipant {
     pub user: Arc<User>,
     pub peer_id: proto::PeerId,
+    pub role: proto::ChannelRole,
     pub projects: Vec<proto::ParticipantProject>,
     pub location: ParticipantLocation,
     pub participant_index: ParticipantIndex,

crates/call/src/room.rs 🔗

@@ -610,6 +610,12 @@ impl Room {
             .find(|p| p.peer_id == peer_id)
     }
 
+    pub fn role_for_user(&self, user_id: u64) -> Option<proto::ChannelRole> {
+        self.remote_participants
+            .get(&user_id)
+            .map(|participant| participant.role)
+    }
+
     pub fn pending_participants(&self) -> &[Arc<User>] {
         &self.pending_participants
     }
@@ -784,6 +790,7 @@ impl Room {
                             });
                         }
 
+                        let role = participant.role();
                         let location = ParticipantLocation::from_proto(participant.location)
                             .unwrap_or(ParticipantLocation::External);
                         if let Some(remote_participant) =
@@ -792,8 +799,11 @@ impl Room {
                             remote_participant.peer_id = peer_id;
                             remote_participant.projects = participant.projects;
                             remote_participant.participant_index = participant_index;
-                            if location != remote_participant.location {
+                            if location != remote_participant.location
+                                || role != remote_participant.role
+                            {
                                 remote_participant.location = location;
+                                remote_participant.role = role;
                                 cx.emit(Event::ParticipantLocationChanged {
                                     participant_id: peer_id,
                                 });
@@ -807,6 +817,7 @@ impl Room {
                                     peer_id,
                                     projects: participant.projects,
                                     location,
+                                    role,
                                     muted: true,
                                     speaking: false,
                                     video_tracks: Default::default(),
@@ -1251,9 +1262,9 @@ impl Room {
             .unwrap_or(false)
     }
 
-    pub fn can_publish(&self) -> bool {
-        self.local_participant().role == proto::ChannelRole::Member
-            || self.local_participant().role == proto::ChannelRole::Admin
+    pub fn read_only(&self) -> bool {
+        !(self.local_participant().role == proto::ChannelRole::Member
+            || self.local_participant().role == proto::ChannelRole::Admin)
     }
 
     pub fn is_speaking(&self) -> bool {

crates/collab/src/tests/channel_guest_tests.rs 🔗

@@ -7,8 +7,8 @@ use workspace::Workspace;
 #[gpui::test]
 async fn test_channel_guests(
     executor: BackgroundExecutor,
-    mut cx_a: &mut TestAppContext,
-    mut cx_b: &mut TestAppContext,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
 ) {
     let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
@@ -37,15 +37,13 @@ async fn test_channel_guests(
         .await;
 
     let active_call_a = cx_a.read(ActiveCall::global);
-    let active_call_b = cx_b.read(ActiveCall::global);
 
     // Client A shares a project in the channel
     active_call_a
         .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
         .await
         .unwrap();
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-    let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap());
+    let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
     let project_id = active_call_a
         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
         .await

crates/collab_ui/src/channel_view.rs 🔗

@@ -172,7 +172,7 @@ impl ChannelView {
                 cx.notify();
             }),
             ChannelBufferEvent::ChannelChanged => {
-                self.editor.update(cx, |editor, cx| {
+                self.editor.update(cx, |_, cx| {
                     cx.emit(editor::EditorEvent::TitleChanged);
                     cx.notify()
                 });

crates/collab_ui/src/collab_panel.rs 🔗

@@ -151,6 +151,9 @@ enum ListEntry {
         peer_id: Option<PeerId>,
         is_last: bool,
     },
+    GuestCount {
+        count: usize,
+    },
     IncomingRequest(Arc<User>),
     OutgoingRequest(Arc<User>),
     ChannelInvite(Arc<Channel>),
@@ -380,10 +383,13 @@ impl CollabPanel {
 
             if !self.collapsed_sections.contains(&Section::ActiveCall) {
                 let room = room.read(cx);
+                let mut guest_count_ix = 0;
+                let mut guest_count = if room.read_only() { 1 } else { 0 };
 
                 if let Some(channel_id) = room.channel_id() {
                     self.entries.push(ListEntry::ChannelNotes { channel_id });
-                    self.entries.push(ListEntry::ChannelChat { channel_id })
+                    self.entries.push(ListEntry::ChannelChat { channel_id });
+                    guest_count_ix = self.entries.len();
                 }
 
                 // Populate the active user.
@@ -402,7 +408,7 @@ impl CollabPanel {
                         &Default::default(),
                         executor.clone(),
                     ));
-                    if !matches.is_empty() {
+                    if !matches.is_empty() && !room.read_only() {
                         let user_id = user.id;
                         self.entries.push(ListEntry::CallParticipant {
                             user,
@@ -430,13 +436,21 @@ impl CollabPanel {
                 // Populate remote participants.
                 self.match_candidates.clear();
                 self.match_candidates
-                    .extend(room.remote_participants().iter().map(|(_, participant)| {
-                        StringMatchCandidate {
-                            id: participant.user.id as usize,
-                            string: participant.user.github_login.clone(),
-                            char_bag: participant.user.github_login.chars().collect(),
-                        }
-                    }));
+                    .extend(
+                        room.remote_participants()
+                            .iter()
+                            .filter_map(|(_, participant)| {
+                                if participant.role == proto::ChannelRole::Guest {
+                                    guest_count += 1;
+                                    return None;
+                                }
+                                Some(StringMatchCandidate {
+                                    id: participant.user.id as usize,
+                                    string: participant.user.github_login.clone(),
+                                    char_bag: participant.user.github_login.chars().collect(),
+                                })
+                            }),
+                    );
                 let matches = executor.block(match_strings(
                     &self.match_candidates,
                     &query,
@@ -470,6 +484,10 @@ impl CollabPanel {
                         });
                     }
                 }
+                if guest_count > 0 {
+                    self.entries
+                        .insert(guest_count_ix, ListEntry::GuestCount { count: guest_count });
+                }
 
                 // Populate pending participants.
                 self.match_candidates.clear();
@@ -959,6 +977,34 @@ impl CollabPanel {
             .tooltip(move |cx| Tooltip::text("Open Chat", cx))
     }
 
+    fn render_guest_count(
+        &self,
+        count: usize,
+        is_selected: bool,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        // TODO! disable manage_members for guests.
+        ListItem::new("guest_count")
+            .selected(is_selected)
+            .on_click(cx.listener(move |this, _, cx| {
+                if let Some(channel_id) = ActiveCall::global(cx).read(cx).channel_id(cx) {
+                    this.manage_members(channel_id, cx)
+                }
+            }))
+            .start_slot(
+                h_stack()
+                    .gap_1()
+                    .child(render_tree_branch(false, cx))
+                    .child(""),
+            )
+            .child(Label::new(if count == 1 {
+                format!("{} guest", count)
+            } else {
+                format!("{} guests", count)
+            }))
+            .tooltip(move |cx| Tooltip::text("Manage Members", cx))
+    }
+
     fn has_subchannels(&self, ix: usize) -> bool {
         self.entries.get(ix).map_or(false, |entry| {
             if let ListEntry::Channel { has_children, .. } = entry {
@@ -1180,6 +1226,11 @@ impl CollabPanel {
                             });
                         }
                     }
+                    ListEntry::GuestCount { .. } => {
+                        if let Some(channel_id) = ActiveCall::global(cx).read(cx).channel_id(cx) {
+                            self.manage_members(channel_id, cx)
+                        }
+                    }
                     ListEntry::Channel { channel, .. } => {
                         let is_active = maybe!({
                             let call_channel = ActiveCall::global(cx)
@@ -1735,6 +1786,9 @@ impl CollabPanel {
             ListEntry::ParticipantScreen { peer_id, is_last } => self
                 .render_participant_screen(*peer_id, *is_last, is_selected, cx)
                 .into_any_element(),
+            ListEntry::GuestCount { count } => self
+                .render_guest_count(*count, is_selected, cx)
+                .into_any_element(),
             ListEntry::ChannelNotes { channel_id } => self
                 .render_channel_notes(*channel_id, is_selected, cx)
                 .into_any_element(),
@@ -2504,6 +2558,11 @@ impl PartialEq for ListEntry {
                     return true;
                 }
             }
+            ListEntry::GuestCount { .. } => {
+                if let ListEntry::GuestCount { .. } = other {
+                    return true;
+                }
+            }
         }
         false
     }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -10,6 +10,7 @@ use gpui::{
 };
 use project::{Project, RepositoryEntry};
 use recent_projects::RecentProjects;
+use rpc::proto;
 use std::sync::Arc;
 use theme::{ActiveTheme, PlayerColors};
 use ui::{
@@ -173,9 +174,9 @@ impl Render for CollabTitlebarItem {
                         let is_muted = room.is_muted(cx);
                         let is_deafened = room.is_deafened().unwrap_or(false);
                         let is_screen_sharing = room.is_screen_sharing();
-                        let can_publish = room.can_publish();
+                        let read_only = room.read_only();
 
-                        this.when(is_local && can_publish, |this| {
+                        this.when(is_local && !read_only, |this| {
                             this.child(
                                 Button::new(
                                     "toggle_sharing",
@@ -204,7 +205,7 @@ impl Render for CollabTitlebarItem {
                                         .detach_and_log_err(cx);
                                 }),
                         )
-                        .when(can_publish, |this| {
+                        .when(!read_only, |this| {
                             this.child(
                                 IconButton::new(
                                     "mute-microphone",
@@ -233,7 +234,7 @@ impl Render for CollabTitlebarItem {
                             .icon_size(IconSize::Small)
                             .selected(is_deafened)
                             .tooltip(move |cx| {
-                                if can_publish {
+                                if !read_only {
                                     Tooltip::with_meta(
                                         "Deafen Audio",
                                         None,
@@ -246,7 +247,7 @@ impl Render for CollabTitlebarItem {
                             })
                             .on_click(move |_, cx| crate::toggle_deafen(&Default::default(), cx)),
                         )
-                        .when(can_publish, |this| {
+                        .when(!read_only, |this| {
                             this.child(
                                 IconButton::new("screen-share", ui::Icon::Screen)
                                     .style(ButtonStyle::Subtle)
@@ -420,6 +421,10 @@ impl CollabTitlebarItem {
         project_id: Option<u64>,
         current_user: &Arc<User>,
     ) -> Option<FacePile> {
+        if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
+            return None;
+        }
+
         let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
 
         let pile = FacePile::default()

crates/theme/src/styles/players.rs 🔗

@@ -1,7 +1,7 @@
-use gpui::{hsla, Hsla};
+use gpui::Hsla;
 use serde_derive::Deserialize;
 
-use crate::{amber, blue, gray, jade, lime, orange, pink, purple, red};
+use crate::{amber, blue, jade, lime, orange, pink, purple, red};
 
 #[derive(Debug, Clone, Copy, Deserialize, Default)]
 pub struct PlayerColor {