Cargo.lock 🔗
@@ -7084,6 +7084,7 @@ name = "workspace"
version = "0.1.0"
dependencies = [
"anyhow",
+ "call",
"client",
"collections",
"context_menu",
Antonio Scandurra created
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(-)
@@ -7084,6 +7084,7 @@ name = "workspace"
version = "0.1.0"
dependencies = [
"anyhow",
+ "call",
"client",
"collections",
"context_menu",
@@ -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);
}
}
}
@@ -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,
}
@@ -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"] }
@@ -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;
@@ -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(),
@@ -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),