Revert "Decouple workspace from call (#3380)"

This reverts commit 6da57cbc6e, reversing
changes made to 62b1843704.

Also, adjust new code that was written using the "call handler".
This commit is contained in:
Max Brunsfeld 2023-12-04 16:55:04 -08:00
parent 2c5603032d
commit 959b2961ff
15 changed files with 423 additions and 797 deletions

4
Cargo.lock generated
View file

@ -1222,7 +1222,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-broadcast", "async-broadcast",
"async-trait",
"audio2", "audio2",
"client2", "client2",
"collections", "collections",
@ -1242,9 +1241,7 @@ dependencies = [
"serde_json", "serde_json",
"settings2", "settings2",
"smallvec", "smallvec",
"ui2",
"util", "util",
"workspace2",
] ]
[[package]] [[package]]
@ -11477,7 +11474,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-recursion 1.0.5", "async-recursion 1.0.5",
"async-trait",
"bincode", "bincode",
"call2", "call2",
"client2", "client2",

View file

@ -31,9 +31,7 @@ media = { path = "../media" }
project = { package = "project2", path = "../project2" } project = { package = "project2", path = "../project2" }
settings = { package = "settings2", path = "../settings2" } settings = { package = "settings2", path = "../settings2" }
util = { path = "../util" } util = { path = "../util" }
ui = {package = "ui2", path = "../ui2"}
workspace = {package = "workspace2", path = "../workspace2"}
async-trait.workspace = true
anyhow.workspace = true anyhow.workspace = true
async-broadcast = "0.4" async-broadcast = "0.4"
futures.workspace = true futures.workspace = true

View file

@ -1,32 +1,25 @@
pub mod call_settings; pub mod call_settings;
pub mod participant; pub mod participant;
pub mod room; pub mod room;
mod shared_screen;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_trait::async_trait;
use audio::Audio; use audio::Audio;
use call_settings::CallSettings; use call_settings::CallSettings;
use client::{ use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
proto::{self, PeerId},
Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE,
};
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel, AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle, WeakModel,
}; };
pub use participant::ParticipantLocation;
use postage::watch; use postage::watch;
use project::Project; use project::Project;
use room::Event; use room::Event;
pub use room::Room;
use settings::Settings; use settings::Settings;
use shared_screen::SharedScreen;
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt;
use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; pub use participant::ParticipantLocation;
pub use room::Room;
pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) { pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
CallSettings::register(cx); CallSettings::register(cx);
@ -334,55 +327,12 @@ impl ActiveCall {
pub fn join_channel( pub fn join_channel(
&mut self, &mut self,
channel_id: u64, channel_id: u64,
requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Option<Model<Room>>>> { ) -> Task<Result<Option<Model<Room>>>> {
if let Some(room) = self.room().cloned() { if let Some(room) = self.room().cloned() {
if room.read(cx).channel_id() == Some(channel_id) { if room.read(cx).channel_id() == Some(channel_id) {
return cx.spawn(|_, _| async move { return Task::ready(Ok(Some(room)));
todo!(); } else {
// let future = room.update(&mut cx, |room, cx| {
// room.most_active_project(cx).map(|(host, project)| {
// room.join_project(project, host, app_state.clone(), cx)
// })
// })
// if let Some(future) = future {
// future.await?;
// }
// Ok(Some(room))
});
}
let should_prompt = room.update(cx, |room, _| {
room.channel_id().is_some()
&& room.is_sharing_project()
&& room.remote_participants().len() > 0
});
if should_prompt && requesting_window.is_some() {
return cx.spawn(|this, mut cx| async move {
let answer = requesting_window.unwrap().update(&mut cx, |_, cx| {
cx.prompt(
PromptLevel::Warning,
"Leaving this call will unshare your current project.\nDo you want to switch channels?",
&["Yes, Join Channel", "Cancel"],
)
})?;
if answer.await? == 1 {
return Ok(None);
}
room.update(&mut cx, |room, cx| room.clear_state(cx))?;
this.update(&mut cx, |this, cx| {
this.join_channel(channel_id, requesting_window, cx)
})?
.await
});
}
if room.read(cx).channel_id().is_some() {
room.update(cx, |room, cx| room.clear_state(cx)); room.update(cx, |room, cx| room.clear_state(cx));
} }
} }
@ -555,197 +505,6 @@ pub fn report_call_event_for_channel(
) )
} }
pub struct Call {
active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
}
impl Call {
pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
let mut active_call = None;
if cx.has_global::<Model<ActiveCall>>() {
let call = cx.global::<Model<ActiveCall>>().clone();
let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)];
active_call = Some((call, subscriptions));
}
Box::new(Self { active_call })
}
fn on_active_call_event(
workspace: &mut Workspace,
_: Model<ActiveCall>,
event: &room::Event,
cx: &mut ViewContext<Workspace>,
) {
match event {
room::Event::ParticipantLocationChanged { participant_id }
| room::Event::RemoteVideoTracksChanged { participant_id } => {
workspace.leader_updated(*participant_id, cx);
}
_ => {}
}
}
}
#[async_trait(?Send)]
impl CallHandler for Call {
fn peer_state(
&mut self,
leader_id: PeerId,
project: &Model<Project>,
cx: &mut ViewContext<Workspace>,
) -> Option<(bool, bool)> {
let (call, _) = self.active_call.as_ref()?;
let room = call.read(cx).room()?.read(cx);
let participant = room.remote_participant_for_peer_id(leader_id)?;
let leader_in_this_app;
let leader_in_this_project;
match participant.location {
ParticipantLocation::SharedProject { project_id } => {
leader_in_this_app = true;
leader_in_this_project = Some(project_id) == project.read(cx).remote_id();
}
ParticipantLocation::UnsharedProject => {
leader_in_this_app = true;
leader_in_this_project = false;
}
ParticipantLocation::External => {
leader_in_this_app = false;
leader_in_this_project = false;
}
};
Some((leader_in_this_project, leader_in_this_app))
}
fn shared_screen_for_peer(
&self,
peer_id: PeerId,
pane: &View<Pane>,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
let (call, _) = self.active_call.as_ref()?;
let room = call.read(cx).room()?.read(cx);
let participant = room.remote_participant_for_peer_id(peer_id)?;
let track = participant.video_tracks.values().next()?.clone();
let user = participant.user.clone();
for item in pane.read(cx).items_of_type::<SharedScreen>() {
if item.read(cx).peer_id == peer_id {
return Some(Box::new(item));
}
}
Some(Box::new(cx.build_view(|cx| {
SharedScreen::new(&track, peer_id, user.clone(), cx)
})))
}
fn room_id(&self, cx: &AppContext) -> Option<u64> {
Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
}
fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
let Some((call, _)) = self.active_call.as_ref() else {
return Task::ready(Err(anyhow!("Cannot exit a call; not in a call")));
};
call.update(cx, |this, cx| this.hang_up(cx))
}
fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
ActiveCall::global(cx).read(cx).location().cloned()
}
fn invite(
&mut self,
called_user_id: u64,
initial_project: Option<Model<Project>>,
cx: &mut AppContext,
) -> Task<Result<()>> {
ActiveCall::global(cx).update(cx, |this, cx| {
this.invite(called_user_id, initial_project, cx)
})
}
fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
self.active_call
.as_ref()
.map(|call| {
call.0.read(cx).room().map(|room| {
room.read(cx)
.remote_participants()
.iter()
.map(|participant| {
(participant.1.user.clone(), participant.1.peer_id.clone())
})
.collect()
})
})
.flatten()
}
fn is_muted(&self, cx: &AppContext) -> Option<bool> {
self.active_call
.as_ref()
.map(|call| {
call.0
.read(cx)
.room()
.map(|room| room.read(cx).is_muted(cx))
})
.flatten()
}
fn toggle_mute(&self, cx: &mut AppContext) {
self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| {
this.room().map(|room| {
let room = room.clone();
cx.spawn(|_, mut cx| async move {
room.update(&mut cx, |this, cx| this.toggle_mute(cx))??
.await
})
.detach_and_log_err(cx);
})
})
});
}
fn toggle_screen_share(&self, cx: &mut AppContext) {
self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| {
this.room().map(|room| {
room.update(cx, |this, cx| {
if this.is_screen_sharing() {
this.unshare_screen(cx).log_err();
} else {
let t = this.share_screen(cx);
cx.spawn(move |_, _| async move {
t.await.log_err();
})
.detach();
}
})
})
})
});
}
fn toggle_deafen(&self, cx: &mut AppContext) {
self.active_call.as_ref().map(|call| {
call.0.update(cx, |this, cx| {
this.room().map(|room| {
room.update(cx, |this, cx| {
this.toggle_deafen(cx).log_err();
})
})
})
});
}
fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
self.active_call
.as_ref()
.map(|call| {
call.0
.read(cx)
.room()
.map(|room| room.read(cx).is_deafened())
})
.flatten()
.flatten()
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use gpui::TestAppContext; use gpui::TestAppContext;

View file

@ -4,7 +4,7 @@ use client::{proto, User};
use collections::HashMap; use collections::HashMap;
use gpui::WeakModel; use gpui::WeakModel;
pub use live_kit_client::Frame; pub use live_kit_client::Frame;
pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack};
use project::Project; use project::Project;
use std::sync::Arc; use std::sync::Arc;

View file

@ -364,8 +364,7 @@ async fn test_joining_channel_ancestor_member(
let active_call_b = cx_b.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global);
assert!(active_call_b assert!(active_call_b
.update(cx_b, |active_call, cx| active_call .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
.join_channel(sub_id, None, cx))
.await .await
.is_ok()); .is_ok());
} }
@ -395,9 +394,7 @@ async fn test_channel_room(
let active_call_b = cx_b.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global);
active_call_a active_call_a
.update(cx_a, |active_call, cx| { .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
active_call.join_channel(zed_id, None, cx)
})
.await .await
.unwrap(); .unwrap();
@ -445,9 +442,7 @@ async fn test_channel_room(
}); });
active_call_b active_call_b
.update(cx_b, |active_call, cx| { .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
active_call.join_channel(zed_id, None, cx)
})
.await .await
.unwrap(); .unwrap();
@ -564,16 +559,12 @@ async fn test_channel_room(
}); });
active_call_a active_call_a
.update(cx_a, |active_call, cx| { .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
active_call.join_channel(zed_id, None, cx)
})
.await .await
.unwrap(); .unwrap();
active_call_b active_call_b
.update(cx_b, |active_call, cx| { .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
active_call.join_channel(zed_id, None, cx)
})
.await .await
.unwrap(); .unwrap();
@ -617,9 +608,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo
let active_call_a = cx_a.read(ActiveCall::global); let active_call_a = cx_a.read(ActiveCall::global);
active_call_a active_call_a
.update(cx_a, |active_call, cx| { .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
active_call.join_channel(zed_id, None, cx)
})
.await .await
.unwrap(); .unwrap();
@ -638,7 +627,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo
active_call_a active_call_a
.update(cx_a, |active_call, cx| { .update(cx_a, |active_call, cx| {
active_call.join_channel(rust_id, None, cx) active_call.join_channel(rust_id, cx)
}) })
.await .await
.unwrap(); .unwrap();
@ -804,7 +793,7 @@ async fn test_call_from_channel(
let active_call_b = cx_b.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global);
active_call_a active_call_a
.update(cx_a, |call, cx| call.join_channel(channel_id, None, cx)) .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
.await .await
.unwrap(); .unwrap();
@ -1297,7 +1286,7 @@ async fn test_guest_access(
// Non-members should not be allowed to join // Non-members should not be allowed to join
assert!(active_call_b assert!(active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
.await .await
.is_err()); .is_err());
@ -1319,7 +1308,7 @@ async fn test_guest_access(
// Client B joins channel A as a guest // Client B joins channel A as a guest
active_call_b active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) .update(cx_b, |call, cx| call.join_channel(channel_a, cx))
.await .await
.unwrap(); .unwrap();
@ -1352,7 +1341,7 @@ async fn test_guest_access(
assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); assert_channels_list_shape(client_b.channel_store(), cx_b, &[]);
active_call_b active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_b, None, cx)) .update(cx_b, |call, cx| call.join_channel(channel_b, cx))
.await .await
.unwrap(); .unwrap();
@ -1383,7 +1372,7 @@ async fn test_invite_access(
// should not be allowed to join // should not be allowed to join
assert!(active_call_b assert!(active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await .await
.is_err()); .is_err());
@ -1401,7 +1390,7 @@ async fn test_invite_access(
.unwrap(); .unwrap();
active_call_b active_call_b
.update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx))
.await .await
.unwrap(); .unwrap();

View file

@ -510,10 +510,9 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
// Simultaneously join channel 1 and then channel 2 // Simultaneously join channel 1 and then channel 2
active_call_a active_call_a
.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)) .update(cx_a, |call, cx| call.join_channel(channel_1, cx))
.detach(); .detach();
let join_channel_2 = let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx));
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, None, cx));
join_channel_2.await.unwrap(); join_channel_2.await.unwrap();
@ -539,8 +538,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
call.invite(client_c.user_id().unwrap(), None, cx) call.invite(client_c.user_id().unwrap(), None, cx)
}); });
let join_channel = let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
b_invite.await.unwrap(); b_invite.await.unwrap();
c_invite.await.unwrap(); c_invite.await.unwrap();
@ -569,8 +567,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously(
.unwrap(); .unwrap();
// Simultaneously join channel 1 and call user B and user C from client A. // Simultaneously join channel 1 and call user B and user C from client A.
let join_channel = let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx));
active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx));
let b_invite = active_call_a.update(cx_a, |call, cx| { let b_invite = active_call_a.update(cx_a, |call, cx| {
call.invite(client_b.user_id().unwrap(), None, cx) call.invite(client_b.user_id().unwrap(), None, cx)

View file

@ -221,7 +221,6 @@ impl TestServer {
fs: fs.clone(), fs: fs.clone(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
call_factory: |_| Box::new(workspace::TestCallHandler),
}); });
cx.update(|cx| { cx.update(|cx| {

View file

@ -1199,7 +1199,6 @@ impl CollabPanel {
.on_click(cx.listener(move |this, _, cx| { .on_click(cx.listener(move |this, _, cx| {
this.workspace.update(cx, |workspace, cx| { this.workspace.update(cx, |workspace, cx| {
let app_state = workspace.app_state().clone(); let app_state = workspace.app_state().clone();
let call = workspace.call_state();
workspace::join_remote_project(project_id, host_user_id, app_state, cx) workspace::join_remote_project(project_id, host_user_id, app_state, cx)
.detach_and_log_err(cx); .detach_and_log_err(cx);
}); });
@ -2219,20 +2218,19 @@ impl CollabPanel {
} }
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) { fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
let Some(workspace) = self.workspace.upgrade() else {
return;
};
let Some(handle) = cx.window_handle().downcast::<Workspace>() else { let Some(handle) = cx.window_handle().downcast::<Workspace>() else {
return; return;
}; };
let active_call = ActiveCall::global(cx); workspace::join_channel(
cx.spawn(|_, mut cx| async move { channel_id,
active_call workspace.read(cx).app_state().clone(),
.update(&mut cx, |active_call, cx| { Some(handle),
active_call.join_channel(channel_id, Some(handle), cx) cx,
}) )
.log_err()? .detach_and_log_err(cx)
.await
.notify_async_err(&mut cx)
})
.detach()
} }
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) { fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
@ -2500,15 +2498,7 @@ impl CollabPanel {
let user_id = contact.user.id; let user_id = contact.user.id;
let github_login = SharedString::from(contact.user.github_login.clone()); let github_login = SharedString::from(contact.user.github_login.clone());
let mut item = ListItem::new(github_login.clone()) let mut item = ListItem::new(github_login.clone())
.on_click(cx.listener(move |this, _, cx| { .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx)))
this.workspace
.update(cx, |this, cx| {
this.call_state()
.invite(user_id, None, cx)
.detach_and_log_err(cx)
})
.log_err();
}))
.child( .child(
h_stack() h_stack()
.w_full() .w_full()

View file

@ -99,37 +99,23 @@ impl Render for CollabTitlebarItem {
type Element = Stateful<Div>; type Element = Stateful<Div>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let is_in_room = self let room = ActiveCall::global(cx).read(cx).room();
.workspace let is_in_room = room.is_some();
.update(cx, |this, cx| this.call_state().is_in_room(cx))
.unwrap_or_default();
let is_shared = is_in_room && self.project.read(cx).is_shared(); let is_shared = is_in_room && self.project.read(cx).is_shared();
let current_user = self.user_store.read(cx).current_user(); let current_user = self.user_store.read(cx).current_user();
let client = self.client.clone(); let client = self.client.clone();
let users = self let remote_participants = room.map(|room| {
.workspace room.read(cx)
.update(cx, |this, cx| this.call_state().remote_participants(cx)) .remote_participants()
.log_err() .values()
.flatten(); .map(|participant| (participant.user.clone(), participant.peer_id))
let is_muted = self .collect::<Vec<_>>()
.workspace });
.update(cx, |this, cx| this.call_state().is_muted(cx)) let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx));
.log_err() let is_deafened = room
.flatten() .and_then(|room| room.read(cx).is_deafened())
.unwrap_or_default(); .unwrap_or(false);
let is_deafened = self let speakers_icon = if is_deafened {
.workspace
.update(cx, |this, cx| this.call_state().is_deafened(cx))
.log_err()
.flatten()
.unwrap_or_default();
let speakers_icon = if self
.workspace
.update(cx, |this, cx| this.call_state().is_deafened(cx))
.log_err()
.flatten()
.unwrap_or_default()
{
ui::Icon::AudioOff ui::Icon::AudioOff
} else { } else {
ui::Icon::AudioOn ui::Icon::AudioOn
@ -165,7 +151,7 @@ impl Render for CollabTitlebarItem {
.children(self.render_project_branch(cx)), .children(self.render_project_branch(cx)),
) )
.when_some( .when_some(
users.zip(current_user.clone()), remote_participants.zip(current_user.clone()),
|this, (remote_participants, current_user)| { |this, (remote_participants, current_user)| {
let mut pile = FacePile::default(); let mut pile = FacePile::default();
pile.extend( pile.extend(
@ -176,25 +162,30 @@ impl Render for CollabTitlebarItem {
div().child(Avatar::data(avatar.clone())).into_any_element() div().child(Avatar::data(avatar.clone())).into_any_element()
}) })
.into_iter() .into_iter()
.chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { .chain(remote_participants.into_iter().filter_map(
user.avatar.as_ref().map(|avatar| { |(user, peer_id)| {
div() let avatar = user.avatar.as_ref()?;
.child( Some(
Avatar::data(avatar.clone()).into_element().into_any(), div()
) .child(
.on_mouse_down(MouseButton::Left, { Avatar::data(avatar.clone())
let workspace = workspace.clone(); .into_element()
move |_, cx| { .into_any(),
workspace )
.update(cx, |this, cx| { .on_mouse_down(MouseButton::Left, {
this.open_shared_screen(peer_id, cx); let workspace = workspace.clone();
}) move |_, cx| {
.log_err(); workspace
} .update(cx, |this, cx| {
}) this.open_shared_screen(peer_id, cx);
.into_any_element() })
}) .log_err();
})), }
})
.into_any_element(),
)
},
)),
); );
this.child(pile.render(cx)) this.child(pile.render(cx))
}, },
@ -226,15 +217,10 @@ impl Render for CollabTitlebarItem {
.child( .child(
IconButton::new("leave-call", ui::Icon::Exit) IconButton::new("leave-call", ui::Icon::Exit)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.on_click({ .on_click(move |_, cx| {
let workspace = workspace.clone(); ActiveCall::global(cx)
move |_, cx| { .update(cx, |call, cx| call.hang_up(cx))
workspace .detach_and_log_err(cx);
.update(cx, |this, cx| {
this.call_state().hang_up(cx).detach();
})
.log_err();
}
}), }),
), ),
) )
@ -252,15 +238,8 @@ impl Render for CollabTitlebarItem {
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.selected(is_muted) .selected(is_muted)
.on_click({ .on_click(move |_, cx| {
let workspace = workspace.clone(); crate::toggle_mute(&Default::default(), cx)
move |_, cx| {
workspace
.update(cx, |this, cx| {
this.call_state().toggle_mute(cx);
})
.log_err();
}
}), }),
) )
.child( .child(
@ -275,26 +254,15 @@ impl Render for CollabTitlebarItem {
cx, cx,
) )
}) })
.on_click({ .on_click(move |_, cx| {
let workspace = workspace.clone(); crate::toggle_mute(&Default::default(), cx)
move |_, cx| {
workspace
.update(cx, |this, cx| {
this.call_state().toggle_deafen(cx);
})
.log_err();
}
}), }),
) )
.child( .child(
IconButton::new("screen-share", ui::Icon::Screen) IconButton::new("screen-share", ui::Icon::Screen)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.on_click(move |_, cx| { .on_click(move |_, cx| {
workspace crate::toggle_screen_sharing(&Default::default(), cx)
.update(cx, |this, cx| {
this.call_state().toggle_screen_share(cx);
})
.log_err();
}), }),
) )
.pl_2(), .pl_2(),

View file

@ -9,22 +9,21 @@ mod panel_settings;
use std::{rc::Rc, sync::Arc}; use std::{rc::Rc, sync::Arc};
use call::{report_call_event_for_room, ActiveCall, Room};
pub use collab_panel::CollabPanel; pub use collab_panel::CollabPanel;
pub use collab_titlebar_item::CollabTitlebarItem; pub use collab_titlebar_item::CollabTitlebarItem;
use gpui::{ use gpui::{
point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds,
WindowOptions, WindowKind, WindowOptions,
}; };
pub use panel_settings::{ pub use panel_settings::{
ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
}; };
use settings::Settings; use settings::Settings;
use util::ResultExt;
use workspace::AppState; use workspace::AppState;
// actions!( actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall);
// collab,
// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
// );
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
CollaborationPanelSettings::register(cx); CollaborationPanelSettings::register(cx);
@ -42,61 +41,61 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
// cx.add_global_action(toggle_deafen); // cx.add_global_action(toggle_deafen);
} }
// pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
// let call = ActiveCall::global(cx).read(cx); let call = ActiveCall::global(cx).read(cx);
// if let Some(room) = call.room().cloned() { if let Some(room) = call.room().cloned() {
// let client = call.client(); let client = call.client();
// let toggle_screen_sharing = room.update(cx, |room, cx| { let toggle_screen_sharing = room.update(cx, |room, cx| {
// if room.is_screen_sharing() { if room.is_screen_sharing() {
// report_call_event_for_room( report_call_event_for_room(
// "disable screen share", "disable screen share",
// room.id(), room.id(),
// room.channel_id(), room.channel_id(),
// &client, &client,
// cx, cx,
// ); );
// Task::ready(room.unshare_screen(cx)) Task::ready(room.unshare_screen(cx))
// } else { } else {
// report_call_event_for_room( report_call_event_for_room(
// "enable screen share", "enable screen share",
// room.id(), room.id(),
// room.channel_id(), room.channel_id(),
// &client, &client,
// cx, cx,
// ); );
// room.share_screen(cx) room.share_screen(cx)
// } }
// }); });
// toggle_screen_sharing.detach_and_log_err(cx); toggle_screen_sharing.detach_and_log_err(cx);
// } }
// } }
// pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
// let call = ActiveCall::global(cx).read(cx); let call = ActiveCall::global(cx).read(cx);
// if let Some(room) = call.room().cloned() { if let Some(room) = call.room().cloned() {
// let client = call.client(); let client = call.client();
// room.update(cx, |room, cx| { room.update(cx, |room, cx| {
// let operation = if room.is_muted(cx) { let operation = if room.is_muted(cx) {
// "enable microphone" "enable microphone"
// } else { } else {
// "disable microphone" "disable microphone"
// }; };
// report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx); report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
// room.toggle_mute(cx) room.toggle_mute(cx)
// }) })
// .map(|task| task.detach_and_log_err(cx)) .map(|task| task.detach_and_log_err(cx))
// .log_err(); .log_err();
// } }
// } }
// pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
// room.update(cx, Room::toggle_deafen) room.update(cx, Room::toggle_deafen)
// .map(|task| task.detach_and_log_err(cx)) .map(|task| task.detach_and_log_err(cx))
// .log_err(); .log_err();
// } }
// } }
fn notification_window_options( fn notification_window_options(
screen: Rc<dyn PlatformDisplay>, screen: Rc<dyn PlatformDisplay>,

View file

@ -20,6 +20,7 @@ test-support = [
[dependencies] [dependencies]
db = { path = "../db2", package = "db2" } db = { path = "../db2", package = "db2" }
call = { path = "../call2", package = "call2" }
client = { path = "../client2", package = "client2" } client = { path = "../client2", package = "client2" }
collections = { path = "../collections" } collections = { path = "../collections" }
# context_menu = { path = "../context_menu" } # context_menu = { path = "../context_menu" }
@ -36,7 +37,6 @@ theme = { path = "../theme2", package = "theme2" }
util = { path = "../util" } util = { path = "../util" }
ui = { package = "ui2", path = "../ui2" } ui = { package = "ui2", path = "../ui2" }
async-trait.workspace = true
async-recursion = "1.0.0" async-recursion = "1.0.0"
itertools = "0.10" itertools = "0.10"
bincode = "1.2.1" bincode = "1.2.1"

View file

@ -1,5 +1,6 @@
use crate::{AppState, FollowerState, Pane, Workspace}; use crate::{AppState, FollowerState, Pane, Workspace};
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use call::ActiveCall;
use collections::HashMap; use collections::HashMap;
use db::sqlez::{ use db::sqlez::{
bindable::{Bind, Column, StaticColumnCount}, bindable::{Bind, Column, StaticColumnCount},
@ -126,6 +127,7 @@ impl PaneGroup {
&self, &self,
project: &Model<Project>, project: &Model<Project>,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,
@ -135,6 +137,7 @@ impl PaneGroup {
project, project,
0, 0,
follower_states, follower_states,
active_call,
active_pane, active_pane,
zoomed, zoomed,
app_state, app_state,
@ -196,6 +199,7 @@ impl Member {
project: &Model<Project>, project: &Model<Project>,
basis: usize, basis: usize,
follower_states: &HashMap<View<Pane>, FollowerState>, follower_states: &HashMap<View<Pane>, FollowerState>,
active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>, active_pane: &View<Pane>,
zoomed: Option<&AnyWeakView>, zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>, app_state: &Arc<AppState>,

View file

@ -1,5 +1,9 @@
use crate::participant::{Frame, RemoteVideoTrack}; use crate::{
item::{Item, ItemEvent},
ItemNavHistory, WorkspaceId,
};
use anyhow::Result; use anyhow::Result;
use call::participant::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User}; use client::{proto::PeerId, User};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
@ -9,7 +13,6 @@ use gpui::{
}; };
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use ui::{h_stack, Icon, IconElement}; use ui::{h_stack, Icon, IconElement};
use workspace::{item::Item, ItemNavHistory, WorkspaceId};
pub enum Event { pub enum Event {
Close, Close,
@ -56,7 +59,7 @@ impl SharedScreen {
} }
impl EventEmitter<Event> for SharedScreen {} impl EventEmitter<Event> for SharedScreen {}
impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {} impl EventEmitter<ItemEvent> for SharedScreen {}
impl FocusableView for SharedScreen { impl FocusableView for SharedScreen {
fn focus_handle(&self, _: &AppContext) -> FocusHandle { fn focus_handle(&self, _: &AppContext) -> FocusHandle {

View file

@ -10,15 +10,16 @@ mod persistence;
pub mod searchable; pub mod searchable;
// todo!() // todo!()
mod modal_layer; mod modal_layer;
mod shared_screen;
mod status_bar; mod status_bar;
mod toolbar; mod toolbar;
mod workspace_settings; mod workspace_settings;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use async_trait::async_trait; use call::ActiveCall;
use client::{ use client::{
proto::{self, PeerId}, proto::{self, PeerId},
Client, TypedEnvelope, User, UserStore, Client, Status, TypedEnvelope, UserStore,
}; };
use collections::{hash_map, HashMap, HashSet}; use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
@ -28,11 +29,11 @@ use futures::{
Future, FutureExt, StreamExt, Future, FutureExt, StreamExt,
}; };
use gpui::{ use gpui::{
actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext,
AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter,
FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model,
ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled,
View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
WindowHandle, WindowOptions, WindowHandle, WindowOptions,
}; };
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
@ -52,6 +53,7 @@ use postage::stream::Stream;
use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use shared_screen::SharedScreen;
use status_bar::StatusBar; use status_bar::StatusBar;
pub use status_bar::StatusItemView; pub use status_bar::StatusItemView;
use std::{ use std::{
@ -209,6 +211,7 @@ pub fn init_settings(cx: &mut AppContext) {
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
init_settings(cx); init_settings(cx);
notifications::init(cx); notifications::init(cx);
// cx.add_global_action({ // cx.add_global_action({
// let app_state = Arc::downgrade(&app_state); // let app_state = Arc::downgrade(&app_state);
// move |_: &Open, cx: &mut AppContext| { // move |_: &Open, cx: &mut AppContext| {
@ -302,7 +305,6 @@ pub struct AppState {
pub user_store: Model<UserStore>, pub user_store: Model<UserStore>,
pub workspace_store: Model<WorkspaceStore>, pub workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs::Fs>, pub fs: Arc<dyn fs::Fs>,
pub call_factory: CallFactory,
pub build_window_options: pub build_window_options:
fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions, fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
pub node_runtime: Arc<dyn NodeRuntime>, pub node_runtime: Arc<dyn NodeRuntime>,
@ -321,69 +323,6 @@ struct Follower {
peer_id: PeerId, peer_id: PeerId,
} }
#[cfg(any(test, feature = "test-support"))]
pub struct TestCallHandler;
#[cfg(any(test, feature = "test-support"))]
impl CallHandler for TestCallHandler {
fn peer_state(
&mut self,
id: PeerId,
project: &Model<Project>,
cx: &mut ViewContext<Workspace>,
) -> Option<(bool, bool)> {
None
}
fn shared_screen_for_peer(
&self,
peer_id: PeerId,
pane: &View<Pane>,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>> {
None
}
fn room_id(&self, cx: &AppContext) -> Option<u64> {
None
}
fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
Task::ready(Err(anyhow!("TestCallHandler should not be hanging up")))
}
fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
None
}
fn invite(
&mut self,
called_user_id: u64,
initial_project: Option<Model<Project>>,
cx: &mut AppContext,
) -> Task<Result<()>> {
unimplemented!()
}
fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
None
}
fn is_muted(&self, cx: &AppContext) -> Option<bool> {
None
}
fn toggle_mute(&self, cx: &mut AppContext) {}
fn toggle_screen_share(&self, cx: &mut AppContext) {}
fn toggle_deafen(&self, cx: &mut AppContext) {}
fn is_deafened(&self, cx: &AppContext) -> Option<bool> {
None
}
}
impl AppState { impl AppState {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Arc<Self> { pub fn test(cx: &mut AppContext) -> Arc<Self> {
@ -414,7 +353,6 @@ impl AppState {
workspace_store, workspace_store,
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
call_factory: |_| Box::new(TestCallHandler),
}) })
} }
} }
@ -471,40 +409,6 @@ pub enum Event {
WorkspaceCreated(WeakView<Workspace>), WorkspaceCreated(WeakView<Workspace>),
} }
#[async_trait(?Send)]
pub trait CallHandler {
fn peer_state(
&mut self,
id: PeerId,
project: &Model<Project>,
cx: &mut ViewContext<Workspace>,
) -> Option<(bool, bool)>;
fn shared_screen_for_peer(
&self,
peer_id: PeerId,
pane: &View<Pane>,
cx: &mut ViewContext<Workspace>,
) -> Option<Box<dyn ItemHandle>>;
fn room_id(&self, cx: &AppContext) -> Option<u64>;
fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
self.room_id(cx).is_some()
}
fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>>;
fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
fn invite(
&mut self,
called_user_id: u64,
initial_project: Option<Model<Project>>,
cx: &mut AppContext,
) -> Task<Result<()>>;
fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>>;
fn is_muted(&self, cx: &AppContext) -> Option<bool>;
fn is_deafened(&self, cx: &AppContext) -> Option<bool>;
fn toggle_mute(&self, cx: &mut AppContext);
fn toggle_deafen(&self, cx: &mut AppContext);
fn toggle_screen_share(&self, cx: &mut AppContext);
}
pub struct Workspace { pub struct Workspace {
window_self: WindowHandle<Self>, window_self: WindowHandle<Self>,
weak_self: WeakView<Self>, weak_self: WeakView<Self>,
@ -525,10 +429,10 @@ pub struct Workspace {
titlebar_item: Option<AnyView>, titlebar_item: Option<AnyView>,
notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>, notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
project: Model<Project>, project: Model<Project>,
call_handler: Box<dyn CallHandler>,
follower_states: HashMap<View<Pane>, FollowerState>, follower_states: HashMap<View<Pane>, FollowerState>,
last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>, last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
window_edited: bool, window_edited: bool,
active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId, database_id: WorkspaceId,
app_state: Arc<AppState>, app_state: Arc<AppState>,
@ -556,7 +460,6 @@ struct FollowerState {
enum WorkspaceBounds {} enum WorkspaceBounds {}
type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
impl Workspace { impl Workspace {
pub fn new( pub fn new(
workspace_id: WorkspaceId, workspace_id: WorkspaceId,
@ -648,19 +551,9 @@ impl Workspace {
mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
let _apply_leader_updates = cx.spawn(|this, mut cx| async move { let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
while let Some((leader_id, update)) = leader_updates_rx.next().await { while let Some((leader_id, update)) = leader_updates_rx.next().await {
let mut cx2 = cx.clone(); Self::process_leader_update(&this, leader_id, update, &mut cx)
let t = this.clone();
Workspace::process_leader_update(&this, leader_id, update, &mut cx)
.await .await
.log_err(); .log_err();
// this.update(&mut cx, |this, cxx| {
// this.call_handler
// .process_leader_update(leader_id, update, cx2)
// })?
// .await
// .log_err();
} }
Ok(()) Ok(())
@ -693,6 +586,14 @@ impl Workspace {
// drag_and_drop.register_container(weak_handle.clone()); // drag_and_drop.register_container(weak_handle.clone());
// }); // });
let mut active_call = None;
if cx.has_global::<Model<ActiveCall>>() {
let call = cx.global::<Model<ActiveCall>>().clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
active_call = Some((call, subscriptions));
}
let subscriptions = vec![ let subscriptions = vec![
cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_activation(Self::on_window_activation_changed),
cx.observe_window_bounds(move |_, cx| { cx.observe_window_bounds(move |_, cx| {
@ -769,8 +670,7 @@ impl Workspace {
follower_states: Default::default(), follower_states: Default::default(),
last_leaders_by_pane: Default::default(), last_leaders_by_pane: Default::default(),
window_edited: false, window_edited: false,
active_call,
call_handler: (app_state.call_factory)(cx),
database_id: workspace_id, database_id: workspace_id,
app_state, app_state,
_observe_current_user, _observe_current_user,
@ -1217,7 +1117,7 @@ impl Workspace {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
//todo!(saveing) //todo!(saveing)
let active_call = self.active_call().cloned();
let window = cx.window_handle(); let window = cx.window_handle();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
@ -1228,27 +1128,27 @@ impl Workspace {
.count() .count()
})?; })?;
if !quitting if let Some(active_call) = active_call {
&& workspace_count == 1 if !quitting
&& this && workspace_count == 1
.update(&mut cx, |this, cx| this.call_handler.is_in_room(cx)) && active_call.read_with(&cx, |call, _| call.room().is_some())?
.log_err() {
.unwrap_or_default() let answer = window.update(&mut cx, |_, cx| {
{ cx.prompt(
let answer = window.update(&mut cx, |_, cx| { PromptLevel::Warning,
cx.prompt( "Do you want to leave the current call?",
PromptLevel::Warning, &["Close window and hang up", "Cancel"],
"Do you want to leave the current call?", )
&["Close window and hang up", "Cancel"], })?;
)
})?;
if answer.await.log_err() == Some(1) { if answer.await.log_err() == Some(1) {
return anyhow::Ok(false); return anyhow::Ok(false);
} else { } else {
this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))? active_call
.await .update(&mut cx, |call, cx| call.hang_up(cx))?
.log_err(); .await
.log_err();
}
} }
} }
@ -2032,7 +1932,7 @@ impl Workspace {
pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) { pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
self.active_pane.update(cx, |pane, cx| { self.active_pane.update(cx, |pane, cx| {
pane.add_item(shared_screen, false, true, None, cx) pane.add_item(Box::new(shared_screen), false, true, None, cx)
}); });
} }
} }
@ -2510,19 +2410,19 @@ impl Workspace {
// } // }
pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> { pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
let follower_states = &mut self.follower_states; let state = self.follower_states.remove(pane)?;
let state = follower_states.remove(pane)?;
let leader_id = state.leader_id; let leader_id = state.leader_id;
for (_, item) in state.items_by_leader_view_id { for (_, item) in state.items_by_leader_view_id {
item.set_leader_peer_id(None, cx); item.set_leader_peer_id(None, cx);
} }
if follower_states if self
.follower_states
.values() .values()
.all(|state| state.leader_id != state.leader_id) .all(|state| state.leader_id != state.leader_id)
{ {
let project_id = self.project.read(cx).remote_id(); let project_id = self.project.read(cx).remote_id();
let room_id = self.call_handler.room_id(cx)?; let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
self.app_state self.app_state
.client .client
.send(proto::Unfollow { .send(proto::Unfollow {
@ -2878,9 +2778,8 @@ impl Workspace {
} else { } else {
None None
}; };
let room_id = self.call_handler.room_id(cx)?;
self.app_state().workspace_store.update(cx, |store, cx| { self.app_state().workspace_store.update(cx, |store, cx| {
store.update_followers(project_id, room_id, update, cx) store.update_followers(project_id, update, cx)
}) })
} }
@ -2888,12 +2787,31 @@ impl Workspace {
self.follower_states.get(pane).map(|state| state.leader_id) self.follower_states.get(pane).map(|state| state.leader_id)
} }
pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> { fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
cx.notify(); cx.notify();
let (leader_in_this_project, leader_in_this_app) = let call = self.active_call()?;
self.call_handler.peer_state(leader_id, &self.project, cx)?; let room = call.read(cx).room()?.read(cx);
let participant = room.remote_participant_for_peer_id(leader_id)?;
let mut items_to_activate = Vec::new(); let mut items_to_activate = Vec::new();
let leader_in_this_app;
let leader_in_this_project;
match participant.location {
call::ParticipantLocation::SharedProject { project_id } => {
leader_in_this_app = true;
leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
}
call::ParticipantLocation::UnsharedProject => {
leader_in_this_app = true;
leader_in_this_project = false;
}
call::ParticipantLocation::External => {
leader_in_this_app = false;
leader_in_this_project = false;
}
};
for (pane, state) in &self.follower_states { for (pane, state) in &self.follower_states {
if state.leader_id != leader_id { if state.leader_id != leader_id {
continue; continue;
@ -2914,7 +2832,7 @@ impl Workspace {
} }
if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
items_to_activate.push((pane.clone(), shared_screen)); items_to_activate.push((pane.clone(), Box::new(shared_screen)));
} }
} }
@ -2923,8 +2841,8 @@ impl Workspace {
if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
} else { } else {
pane.update(cx, |pane, mut cx| { pane.update(cx, |pane, cx| {
pane.add_item(item.boxed_clone(), false, false, None, &mut cx) pane.add_item(item.boxed_clone(), false, false, None, cx)
}); });
} }
@ -2941,21 +2859,20 @@ impl Workspace {
peer_id: PeerId, peer_id: PeerId,
pane: &View<Pane>, pane: &View<Pane>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<Box<dyn ItemHandle>> { ) -> Option<View<SharedScreen>> {
self.call_handler.shared_screen_for_peer(peer_id, pane, cx) let call = self.active_call()?;
// let call = self.active_call()?; let room = call.read(cx).room()?.read(cx);
// let room = call.read(cx).room()?.read(cx); let participant = room.remote_participant_for_peer_id(peer_id)?;
// let participant = room.remote_participant_for_peer_id(peer_id)?; let track = participant.video_tracks.values().next()?.clone();
// let track = participant.video_tracks.values().next()?.clone(); let user = participant.user.clone();
// let user = participant.user.clone();
// for item in pane.read(cx).items_of_type::<SharedScreen>() { for item in pane.read(cx).items_of_type::<SharedScreen>() {
// if item.read(cx).peer_id == peer_id { if item.read(cx).peer_id == peer_id {
// return Some(item); return Some(item);
// } }
// } }
// Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
} }
pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) { pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
@ -2984,6 +2901,25 @@ impl Workspace {
} }
} }
fn active_call(&self) -> Option<&Model<ActiveCall>> {
self.active_call.as_ref().map(|(call, _)| call)
}
fn on_active_call_event(
&mut self,
_: Model<ActiveCall>,
event: &call::room::Event,
cx: &mut ViewContext<Self>,
) {
match event {
call::room::Event::ParticipantLocationChanged { participant_id }
| call::room::Event::RemoteVideoTracksChanged { participant_id } => {
self.leader_updated(*participant_id, cx);
}
_ => {}
}
}
pub fn database_id(&self) -> WorkspaceId { pub fn database_id(&self) -> WorkspaceId {
self.database_id self.database_id
} }
@ -3393,7 +3329,6 @@ impl Workspace {
fs: project.read(cx).fs().clone(), fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(), build_window_options: |_, _, _| Default::default(),
node_runtime: FakeNodeRuntime::new(), node_runtime: FakeNodeRuntime::new(),
call_factory: |_| Box::new(TestCallHandler),
}); });
let workspace = Self::new(0, project, app_state, cx); let workspace = Self::new(0, project, app_state, cx);
workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
@ -3472,10 +3407,6 @@ impl Workspace {
self.modal_layer self.modal_layer
.update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build)) .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
} }
pub fn call_state(&mut self) -> &mut dyn CallHandler {
&mut *self.call_handler
}
} }
fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> { fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
@ -3676,6 +3607,7 @@ impl Render for Workspace {
.child(self.center.render( .child(self.center.render(
&self.project, &self.project,
&self.follower_states, &self.follower_states,
self.active_call(),
&self.active_pane, &self.active_pane,
self.zoomed.as_ref(), self.zoomed.as_ref(),
&self.app_state, &self.app_state,
@ -3846,10 +3778,14 @@ impl WorkspaceStore {
pub fn update_followers( pub fn update_followers(
&self, &self,
project_id: Option<u64>, project_id: Option<u64>,
room_id: u64,
update: proto::update_followers::Variant, update: proto::update_followers::Variant,
cx: &AppContext, cx: &AppContext,
) -> Option<()> { ) -> Option<()> {
if !cx.has_global::<Model<ActiveCall>>() {
return None;
}
let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
let follower_ids: Vec<_> = self let follower_ids: Vec<_> = self
.followers .followers
.iter() .iter()
@ -3885,17 +3821,9 @@ impl WorkspaceStore {
project_id: envelope.payload.project_id, project_id: envelope.payload.project_id,
peer_id: envelope.original_sender_id()?, peer_id: envelope.original_sender_id()?,
}; };
let active_project = ActiveCall::global(cx).read(cx).location().cloned();
let mut response = proto::FollowResponse::default(); let mut response = proto::FollowResponse::default();
let active_project = this
.workspaces
.iter()
.next()
.and_then(|workspace| {
workspace
.read_with(cx, |this, cx| this.call_handler.active_project(cx))
.log_err()
})
.flatten();
for workspace in &this.workspaces { for workspace in &this.workspaces {
workspace workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
@ -4048,187 +3976,184 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
DB.last_workspace().await.log_err().flatten() DB.last_workspace().await.log_err().flatten()
} }
// async fn join_channel_internal( async fn join_channel_internal(
// channel_id: u64, channel_id: u64,
// app_state: &Arc<AppState>, app_state: &Arc<AppState>,
// requesting_window: Option<WindowHandle<Workspace>>, requesting_window: Option<WindowHandle<Workspace>>,
// active_call: &ModelHandle<ActiveCall>, active_call: &Model<ActiveCall>,
// cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,
// ) -> Result<bool> { ) -> Result<bool> {
// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| {
// let Some(room) = active_call.room().map(|room| room.read(cx)) else { let Some(room) = active_call.room().map(|room| room.read(cx)) else {
// return (false, None); return (false, None);
// }; };
// let already_in_channel = room.channel_id() == Some(channel_id); let already_in_channel = room.channel_id() == Some(channel_id);
// let should_prompt = room.is_sharing_project() let should_prompt = room.is_sharing_project()
// && room.remote_participants().len() > 0 && room.remote_participants().len() > 0
// && !already_in_channel; && !already_in_channel;
// let open_room = if already_in_channel { let open_room = if already_in_channel {
// active_call.room().cloned() active_call.room().cloned()
// } else { } else {
// None None
// }; };
// (should_prompt, open_room) (should_prompt, open_room)
// }); })?;
// if let Some(room) = open_room { if let Some(room) = open_room {
// let task = room.update(cx, |room, cx| { let task = room.update(cx, |room, cx| {
// if let Some((project, host)) = room.most_active_project(cx) { if let Some((project, host)) = room.most_active_project(cx) {
// return Some(join_remote_project(project, host, app_state.clone(), cx)); return Some(join_remote_project(project, host, app_state.clone(), cx));
// } }
// None None
// }); })?;
// if let Some(task) = task { if let Some(task) = task {
// task.await?; task.await?;
// } }
// return anyhow::Ok(true); return anyhow::Ok(true);
// } }
// if should_prompt { if should_prompt {
// if let Some(workspace) = requesting_window { if let Some(workspace) = requesting_window {
// if let Some(window) = workspace.update(cx, |cx| cx.window()) { let answer = workspace.update(cx, |_, cx| {
// let answer = window.prompt( cx.prompt(
// PromptLevel::Warning, PromptLevel::Warning,
// "Leaving this call will unshare your current project.\nDo you want to switch channels?", "Leaving this call will unshare your current project.\nDo you want to switch channels?",
// &["Yes, Join Channel", "Cancel"], &["Yes, Join Channel", "Cancel"],
// cx, )
// ); })?.await;
// if let Some(mut answer) = answer { if answer == Ok(1) {
// if answer.next().await == Some(1) { return Ok(false);
// return Ok(false); }
// } } else {
// } return Ok(false); // unreachable!() hopefully
// } else { }
// return Ok(false); // unreachable!() hopefully }
// }
// } else {
// return Ok(false); // unreachable!() hopefully
// }
// }
// let client = cx.read(|cx| active_call.read(cx).client()); let client = cx.update(|cx| active_call.read(cx).client())?;
// let mut client_status = client.status(); let mut client_status = client.status();
// // this loop will terminate within client::CONNECTION_TIMEOUT seconds. // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
// 'outer: loop { 'outer: loop {
// let Some(status) = client_status.recv().await else { let Some(status) = client_status.recv().await else {
// return Err(anyhow!("error connecting")); return Err(anyhow!("error connecting"));
// }; };
// match status { match status {
// Status::Connecting Status::Connecting
// | Status::Authenticating | Status::Authenticating
// | Status::Reconnecting | Status::Reconnecting
// | Status::Reauthenticating => continue, | Status::Reauthenticating => continue,
// Status::Connected { .. } => break 'outer, Status::Connected { .. } => break 'outer,
// Status::SignedOut => return Err(anyhow!("not signed in")), Status::SignedOut => return Err(anyhow!("not signed in")),
// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
// return Err(anyhow!("zed is offline")) return Err(anyhow!("zed is offline"))
// } }
// } }
// } }
// let room = active_call let room = active_call
// .update(cx, |active_call, cx| { .update(cx, |active_call, cx| {
// active_call.join_channel(channel_id, cx) active_call.join_channel(channel_id, cx)
// }) })?
// .await?; .await?;
// room.update(cx, |room, _| room.room_update_completed()) let Some(room) = room else {
// .await; return anyhow::Ok(true);
};
// let task = room.update(cx, |room, cx| { room.update(cx, |room, _| room.room_update_completed())?
// if let Some((project, host)) = room.most_active_project(cx) { .await;
// return Some(join_remote_project(project, host, app_state.clone(), cx));
// }
// None let task = room.update(cx, |room, cx| {
// }); if let Some((project, host)) = room.most_active_project(cx) {
// if let Some(task) = task { return Some(join_remote_project(project, host, app_state.clone(), cx));
// task.await?; }
// return anyhow::Ok(true);
// }
// anyhow::Ok(false)
// }
// pub fn join_channel( None
// channel_id: u64, })?;
// app_state: Arc<AppState>, if let Some(task) = task {
// requesting_window: Option<WindowHandle<Workspace>>, task.await?;
// cx: &mut AppContext, return anyhow::Ok(true);
// ) -> Task<Result<()>> { }
// let active_call = ActiveCall::global(cx); anyhow::Ok(false)
// cx.spawn(|mut cx| async move { }
// let result = join_channel_internal(
// channel_id,
// &app_state,
// requesting_window,
// &active_call,
// &mut cx,
// )
// .await;
// // join channel succeeded, and opened a window pub fn join_channel(
// if matches!(result, Ok(true)) { channel_id: u64,
// return anyhow::Ok(()); app_state: Arc<AppState>,
// } requesting_window: Option<WindowHandle<Workspace>>,
cx: &mut AppContext,
) -> Task<Result<()>> {
let active_call = ActiveCall::global(cx);
cx.spawn(|mut cx| async move {
let result = join_channel_internal(
channel_id,
&app_state,
requesting_window,
&active_call,
&mut cx,
)
.await;
// if requesting_window.is_some() { // join channel succeeded, and opened a window
// return anyhow::Ok(()); if matches!(result, Ok(true)) {
// } return anyhow::Ok(());
}
// // find an existing workspace to focus and show call controls if requesting_window.is_some() {
// let mut active_window = activate_any_workspace_window(&mut cx); return anyhow::Ok(());
// if active_window.is_none() { }
// // no open workspaces, make one to show the error in (blergh)
// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))
// .await;
// }
// active_window = activate_any_workspace_window(&mut cx); // find an existing workspace to focus and show call controls
// if active_window.is_none() { let mut active_window = activate_any_workspace_window(&mut cx);
// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window if active_window.is_none() {
// } // no open workspaces, make one to show the error in (blergh)
cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
.await?;
}
// if let Err(err) = result { active_window = activate_any_workspace_window(&mut cx);
// let prompt = active_window.unwrap().prompt( let Some(active_window) = active_window else {
// PromptLevel::Critical, return anyhow::Ok(());
// &format!("Failed to join channel: {}", err), };
// &["Ok"],
// &mut cx,
// );
// if let Some(mut prompt) = prompt {
// prompt.next().await;
// } else {
// return Err(err);
// }
// }
// // return ok, we showed the error to the user. if let Err(err) = result {
// return anyhow::Ok(()); active_window
// }) .update(&mut cx, |_, cx| {
// } cx.prompt(
PromptLevel::Critical,
&format!("Failed to join channel: {}", err),
&["Ok"],
)
})?
.await
.ok();
}
// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> { // return ok, we showed the error to the user.
// for window in cx.windows() { return anyhow::Ok(());
// let found = window.update(cx, |cx| { })
// let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some(); }
// if is_workspace {
// cx.activate_window(); pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
// } cx.update(|cx| {
// is_workspace for window in cx.windows() {
// }); let is_workspace = window.downcast::<Workspace>().is_some();
// if found == Some(true) { if is_workspace {
// return Some(window); window.update(cx, |_, cx| cx.activate_window()).ok();
// } return Some(window);
// } }
// None }
// } None
})
.ok()
.flatten()
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn open_paths( pub fn open_paths(

View file

@ -191,7 +191,6 @@ fn main() {
user_store: user_store.clone(), user_store: user_store.clone(),
fs, fs,
build_window_options, build_window_options,
call_factory: call::Call::new,
workspace_store, workspace_store,
node_runtime, node_runtime,
}); });