Set room location when active workspace changes

Antonio Scandurra created

Change summary

Cargo.lock                                   |  1 
crates/collab_ui/src/collab_titlebar_item.rs | 41 +++++++++++-
crates/theme/src/theme.rs                    |  1 
crates/workspace/Cargo.toml                  |  9 ++
crates/workspace/src/pane_group.rs           | 74 ++++++++++++++++++---
crates/workspace/src/workspace.rs            |  9 ++
styles/src/styleTree/workspace.ts            |  4 +
7 files changed, 119 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7084,6 +7084,7 @@ name = "workspace"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "call",
  "client",
  "collections",
  "context_menu",

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -76,6 +76,10 @@ impl CollabTitlebarItem {
         let mut subscriptions = Vec::new();
         subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify()));
         subscriptions.push(cx.observe(&active_call, |_, _, cx| cx.notify()));
+        subscriptions.push(cx.observe_window_activation(|this, active, cx| {
+            this.window_activation_changed(active, cx)
+        }));
+
         Self {
             workspace: workspace.downgrade(),
             contacts_popover: None,
@@ -83,14 +87,43 @@ impl CollabTitlebarItem {
         }
     }
 
+    fn window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
+        let workspace = self.workspace.upgrade(cx);
+        let room = ActiveCall::global(cx).read(cx).room().cloned();
+        if let Some((workspace, room)) = workspace.zip(room) {
+            let workspace = workspace.read(cx);
+            let project = if !active {
+                None
+            } else if workspace.project().read(cx).remote_id().is_some() {
+                Some(workspace.project().clone())
+            } else {
+                None
+            };
+
+            room.update(cx, |room, cx| {
+                room.set_location(project.as_ref(), cx)
+                    .detach_and_log_err(cx);
+            });
+        }
+    }
+
     fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
         if let Some(workspace) = self.workspace.upgrade(cx) {
-            if let Some(room) = ActiveCall::global(cx).read(cx).room() {
+            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+                let window_id = cx.window_id();
                 let room_id = room.read(cx).id();
                 let project = workspace.read(cx).project().clone();
-                project
-                    .update(cx, |project, cx| project.share(room_id, cx))
-                    .detach_and_log_err(cx);
+                let share = project.update(cx, |project, cx| project.share(room_id, cx));
+                cx.spawn_weak(|_, mut cx| async move {
+                    share.await?;
+                    if cx.update(|cx| cx.window_is_active(window_id)) {
+                        room.update(&mut cx, |room, cx| {
+                            room.set_location(Some(&project), cx).detach_and_log_err(cx);
+                        });
+                    }
+                    anyhow::Ok(())
+                })
+                .detach_and_log_err(cx);
             }
         }
     }

crates/theme/src/theme.rs 🔗

@@ -58,6 +58,7 @@ pub struct Workspace {
     pub notifications: Notifications,
     pub joining_project_avatar: ImageStyle,
     pub joining_project_message: ContainedText,
+    pub external_location_message: ContainedText,
     pub dock: Dock,
 }
 

crates/workspace/Cargo.toml 🔗

@@ -8,9 +8,15 @@ path = "src/workspace.rs"
 doctest = false
 
 [features]
-test-support = ["client/test-support", "project/test-support", "settings/test-support"]
+test-support = [
+    "call/test-support",
+    "client/test-support",
+    "project/test-support",
+    "settings/test-support"
+]
 
 [dependencies]
+call = { path = "../call" }
 client = { path = "../client" }
 collections = { path = "../collections" }
 context_menu = { path = "../context_menu" }
@@ -32,6 +38,7 @@ serde_json = { version = "1.0", features = ["preserve_order"] }
 smallvec = { version = "1.6", features = ["union"] }
 
 [dev-dependencies]
+call = { path = "../call", features = ["test-support"] }
 client = { path = "../client", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }

crates/workspace/src/pane_group.rs 🔗

@@ -1,8 +1,9 @@
 use crate::{FollowerStatesByLeader, Pane};
 use anyhow::{anyhow, Result};
+use call::ActiveCall;
 use client::PeerId;
 use collections::HashMap;
-use gpui::{elements::*, Axis, Border, ViewHandle};
+use gpui::{elements::*, AppContext, Axis, Border, ViewHandle};
 use project::Collaborator;
 use serde::Deserialize;
 use theme::Theme;
@@ -56,11 +57,14 @@ impl PaneGroup {
 
     pub(crate) fn render(
         &self,
+        project_id: Option<u64>,
         theme: &Theme,
         follower_states: &FollowerStatesByLeader,
         collaborators: &HashMap<PeerId, Collaborator>,
+        cx: &AppContext,
     ) -> ElementBox {
-        self.root.render(theme, follower_states, collaborators)
+        self.root
+            .render(project_id, theme, follower_states, collaborators, cx)
     }
 
     pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
@@ -100,13 +104,14 @@ impl Member {
 
     pub fn render(
         &self,
+        project_id: Option<u64>,
         theme: &Theme,
         follower_states: &FollowerStatesByLeader,
         collaborators: &HashMap<PeerId, Collaborator>,
+        cx: &AppContext,
     ) -> ElementBox {
         match self {
             Member::Pane(pane) => {
-                let mut border = Border::default();
                 let leader = follower_states
                     .iter()
                     .find_map(|(leader_id, follower_states)| {
@@ -116,21 +121,61 @@ impl Member {
                             None
                         }
                     })
-                    .and_then(|leader_id| collaborators.get(leader_id));
-                if let Some(leader) = leader {
-                    let leader_color = theme
-                        .editor
-                        .replica_selection_style(leader.replica_id)
-                        .cursor;
-                    border = Border::all(theme.workspace.leader_border_width, leader_color);
+                    .and_then(|leader_id| {
+                        let room = ActiveCall::global(cx).read(cx).room()?.read(cx);
+                        let collaborator = collaborators.get(leader_id)?;
+                        let participant = room.remote_participants().get(&leader_id)?;
+                        Some((collaborator.replica_id, participant))
+                    });
+
+                if let Some((replica_id, leader)) = leader {
+                    let view = match leader.location {
+                        call::ParticipantLocation::Project {
+                            project_id: leader_project_id,
+                        } => {
+                            if Some(leader_project_id) == project_id {
+                                ChildView::new(pane).boxed()
+                            } else {
+                                Label::new(
+                                    format!(
+                                        "Follow {} on their currently active project",
+                                        leader.user.github_login,
+                                    ),
+                                    theme.workspace.external_location_message.text.clone(),
+                                )
+                                .contained()
+                                .with_style(theme.workspace.external_location_message.container)
+                                .aligned()
+                                .boxed()
+                            }
+                        }
+                        call::ParticipantLocation::External => Label::new(
+                            format!(
+                                "{} is viewing a window outside of Zed",
+                                leader.user.github_login
+                            ),
+                            theme.workspace.external_location_message.text.clone(),
+                        )
+                        .contained()
+                        .with_style(theme.workspace.external_location_message.container)
+                        .aligned()
+                        .boxed(),
+                    };
+
+                    let leader_color = theme.editor.replica_selection_style(replica_id).cursor;
+                    let mut border = Border::all(theme.workspace.leader_border_width, leader_color);
                     border
                         .color
                         .fade_out(1. - theme.workspace.leader_border_opacity);
                     border.overlay = true;
+                    Container::new(view).with_border(border).boxed()
+                } else {
+                    ChildView::new(pane).boxed()
                 }
-                ChildView::new(pane).contained().with_border(border).boxed()
             }
-            Member::Axis(axis) => axis.render(theme, follower_states, collaborators),
+            Member::Axis(axis) => {
+                axis.render(project_id, theme, follower_states, collaborators, cx)
+            }
         }
     }
 
@@ -232,14 +277,17 @@ impl PaneAxis {
 
     fn render(
         &self,
+        project_id: Option<u64>,
         theme: &Theme,
         follower_state: &FollowerStatesByLeader,
         collaborators: &HashMap<PeerId, Collaborator>,
+        cx: &AppContext,
     ) -> ElementBox {
         let last_member_ix = self.members.len() - 1;
         Flex::new(self.axis)
             .with_children(self.members.iter().enumerate().map(|(ix, member)| {
-                let mut member = member.render(theme, follower_state, collaborators);
+                let mut member =
+                    member.render(project_id, theme, follower_state, collaborators, cx);
                 if ix < last_member_ix {
                     let mut border = theme.workspace.pane_divider;
                     border.left = false;

crates/workspace/src/workspace.rs 🔗

@@ -12,7 +12,8 @@ mod status_bar;
 mod toolbar;
 
 use anyhow::{anyhow, Context, Result};
-use client::{proto, Client, Contact, PeerId, Subscription, TypedEnvelope, UserStore};
+use call::ActiveCall;
+use client::{proto, Client, Contact, PeerId, TypedEnvelope, UserStore};
 use collections::{hash_map, HashMap, HashSet};
 use dock::{DefaultItemFactory, Dock, ToggleDockButton};
 use drag_and_drop::DragAndDrop;
@@ -860,7 +861,7 @@ pub struct Workspace {
     weak_self: WeakViewHandle<Self>,
     client: Arc<Client>,
     user_store: ModelHandle<client::UserStore>,
-    remote_entity_subscription: Option<Subscription>,
+    remote_entity_subscription: Option<client::Subscription>,
     fs: Arc<dyn Fs>,
     modal: Option<AnyViewHandle>,
     center: PaneGroup,
@@ -880,6 +881,7 @@ pub struct Workspace {
     last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
     window_edited: bool,
     _observe_current_user: Task<()>,
+    _active_call_observation: gpui::Subscription,
 }
 
 #[derive(Default)]
@@ -1015,6 +1017,7 @@ impl Workspace {
             last_leaders_by_pane: Default::default(),
             window_edited: false,
             _observe_current_user,
+            _active_call_observation: cx.observe(&ActiveCall::global(cx), |_, _, cx| cx.notify()),
         };
         this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
         cx.defer(|this, cx| this.update_window_title(cx));
@@ -2430,9 +2433,11 @@ impl View for Workspace {
                                             Flex::column()
                                                 .with_child(
                                                     FlexItem::new(self.center.render(
+                                                        self.project.read(cx).remote_id(),
                                                         &theme,
                                                         &self.follower_states_by_leader,
                                                         self.project.read(cx).collaborators(),
+                                                        cx,
                                                     ))
                                                     .flex(1., true)
                                                     .boxed(),

styles/src/styleTree/workspace.ts 🔗

@@ -48,6 +48,10 @@ export default function workspace(theme: Theme) {
       padding: 12,
       ...text(theme, "sans", "primary", { size: "lg" }),
     },
+    externalLocationMessage: {
+      padding: 12,
+      ...text(theme, "sans", "primary", { size: "lg" }),
+    },
     leaderBorderOpacity: 0.7,
     leaderBorderWidth: 2.0,
     tabBar: tabBar(theme),