Remove JoinProject internal action

This commit is contained in:
Antonio Scandurra
2023-04-28 11:22:04 +02:00
parent f881f9e3d8
commit d953729233
12 changed files with 302 additions and 287 deletions
+14 -16
View File
@@ -23,7 +23,7 @@ use settings::Settings;
use std::{ops::Range, sync::Arc};
use theme::{AvatarStyle, Theme};
use util::ResultExt;
use workspace::{FollowNextCollaborator, JoinProject, Workspace};
use workspace::{FollowNextCollaborator, Workspace};
actions!(
collab,
@@ -134,21 +134,18 @@ impl View for CollabTitlebarItem {
}
impl CollabTitlebarItem {
pub fn new(
workspace: &ViewHandle<Workspace>,
user_store: &ModelHandle<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let active_call = ActiveCall::global(cx);
let user_store = workspace.read(cx).user_store().clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
subscriptions.push(cx.observe_window_activation(|this, active, cx| {
this.window_activation_changed(active, cx)
}));
subscriptions.push(cx.observe(user_store, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
subscriptions.push(
cx.subscribe(user_store, move |this, user_store, event, cx| {
cx.subscribe(&user_store, move |this, user_store, event, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
if let client::Event::Contact { user, kind } = event {
@@ -257,9 +254,7 @@ impl CollabTitlebarItem {
pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext<Self>) {
if self.contacts_popover.take().is_none() {
if let Some(workspace) = self.workspace.upgrade(cx) {
let project = workspace.read(cx).project().clone();
let user_store = workspace.read(cx).user_store().clone();
let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx));
let view = cx.add_view(|cx| ContactsPopover::new(&workspace, cx));
cx.subscribe(&view, |this, _, event, cx| {
match event {
contacts_popover::Event::Dismissed => {
@@ -776,6 +771,8 @@ impl CollabTitlebarItem {
)
.into_any();
} else if let ParticipantLocation::SharedProject { project_id } = location {
enum JoinProject {}
let user_id = user.id;
content = MouseEventHandler::<JoinProject, Self>::new(
peer_id.as_u64() as usize,
@@ -783,11 +780,12 @@ impl CollabTitlebarItem {
move |_, _| content,
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id,
follow_user_id: user_id,
})
.on_click(MouseButton::Left, move |_, this, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
let app_state = workspace.read(cx).app_state().clone();
workspace::join_remote_project(project_id, user_id, app_state, cx)
.detach_and_log_err(cx);
}
})
.with_tooltip::<JoinProject>(
peer_id.as_u64() as usize,
+4 -93
View File
@@ -10,29 +10,25 @@ mod notifications;
mod project_shared_notification;
mod sharing_status_indicator;
use anyhow::anyhow;
use call::ActiveCall;
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
use gpui::{actions, AppContext, Task};
use std::sync::Arc;
use workspace::{AppState, JoinProject, Workspace};
use workspace::AppState;
actions!(collab, [ToggleScreenSharing]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
collab_titlebar_item::init(cx);
contact_notification::init(cx);
contact_list::init(cx);
contact_finder::init(cx);
contacts_popover::init(cx);
incoming_call_notification::init(cx);
project_shared_notification::init(cx);
incoming_call_notification::init(&app_state, cx);
project_shared_notification::init(&app_state, cx);
sharing_status_indicator::init(cx);
cx.add_global_action(toggle_screen_sharing);
cx.add_global_action(move |action: &JoinProject, cx| {
join_project(action, app_state.clone(), cx);
});
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -47,88 +43,3 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
toggle_screen_sharing.detach_and_log_err(cx);
}
}
fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppContext) {
let project_id = action.project_id;
let follow_user_id = action.follow_user_id;
cx.spawn(|mut cx| async move {
let existing_workspace = cx.update(|cx| {
cx.window_ids()
.filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
.find(|workspace| {
workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
})
});
let workspace = if let Some(existing_workspace) = existing_workspace {
existing_workspace.downgrade()
} else {
let active_call = cx.read(ActiveCall::global);
let room = active_call
.read_with(&cx, |call, _| call.room().cloned())
.ok_or_else(|| anyhow!("not in a call"))?;
let project = room
.update(&mut cx, |room, cx| {
room.join_project(
project_id,
app_state.languages.clone(),
app_state.fs.clone(),
cx,
)
})
.await?;
let (_, workspace) = cx.add_window(
(app_state.build_window_options)(None, None, cx.platform().as_ref()),
|cx| {
let mut workspace = Workspace::new(
Default::default(),
0,
project,
app_state.dock_default_item_factory,
app_state.background_actions,
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
workspace
},
);
workspace.downgrade()
};
cx.activate_window(workspace.window_id());
cx.platform().activate(true);
workspace.update(&mut cx, |workspace, cx| {
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
let follow_peer_id = room
.read(cx)
.remote_participants()
.iter()
.find(|(_, participant)| participant.user.id == follow_user_id)
.map(|(_, p)| p.peer_id)
.or_else(|| {
// If we couldn't follow the given user, follow the host instead.
let collaborator = workspace
.project()
.read(cx)
.collaborators()
.values()
.find(|collaborator| collaborator.replica_id == 0)?;
Some(collaborator.peer_id)
});
if let Some(follow_peer_id) = follow_peer_id {
if !workspace.is_being_followed(follow_peer_id) {
workspace
.toggle_follow(follow_peer_id, cx)
.map(|follow| follow.detach_and_log_err(cx));
}
}
}
})?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
+25 -17
View File
@@ -11,7 +11,7 @@ use gpui::{
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel},
AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::Project;
@@ -19,7 +19,7 @@ use serde::Deserialize;
use settings::Settings;
use std::{mem, sync::Arc};
use theme::IconButton;
use workspace::{JoinProject, OpenSharedScreen};
use workspace::{OpenSharedScreen, Workspace};
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
@@ -161,6 +161,7 @@ pub struct ContactList {
match_candidates: Vec<StringMatchCandidate>,
list_state: ListState<Self>,
project: ModelHandle<Project>,
workspace: WeakViewHandle<Workspace>,
user_store: ModelHandle<UserStore>,
filter_editor: ViewHandle<Editor>,
collapsed_sections: Vec<Section>,
@@ -169,11 +170,7 @@ pub struct ContactList {
}
impl ContactList {
pub fn new(
project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let filter_editor = cx.add_view(|cx| {
let mut editor = Editor::single_line(
Some(Arc::new(|theme| {
@@ -278,6 +275,7 @@ impl ContactList {
});
let active_call = ActiveCall::global(cx);
let user_store = workspace.read(cx).user_store().clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx)));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx)));
@@ -290,7 +288,8 @@ impl ContactList {
match_candidates: Default::default(),
filter_editor,
_subscriptions: subscriptions,
project,
project: workspace.read(cx).project().clone(),
workspace: workspace.downgrade(),
user_store,
};
this.update_entries(cx);
@@ -422,10 +421,16 @@ impl ContactList {
host_user_id,
..
} => {
cx.dispatch_global_action(JoinProject {
project_id: *project_id,
follow_user_id: *host_user_id,
});
if let Some(workspace) = self.workspace.upgrade(cx) {
let app_state = workspace.read(cx).app_state().clone();
workspace::join_remote_project(
*project_id,
*host_user_id,
app_state,
cx,
)
.detach_and_log_err(cx);
}
}
ContactEntry::ParticipantScreen { peer_id, .. } => {
cx.dispatch_action(OpenSharedScreen { peer_id: *peer_id });
@@ -798,6 +803,8 @@ impl ContactList {
theme: &theme::ContactList,
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
enum JoinProject {}
let font_cache = cx.font_cache();
let host_avatar_height = theme
.contact_avatar
@@ -873,12 +880,13 @@ impl ContactList {
} else {
CursorStyle::Arrow
})
.on_click(MouseButton::Left, move |_, _, cx| {
.on_click(MouseButton::Left, move |_, this, cx| {
if !is_current {
cx.dispatch_global_action(JoinProject {
project_id,
follow_user_id: host_user_id,
});
if let Some(workspace) = this.workspace.upgrade(cx) {
let app_state = workspace.read(cx).app_state().clone();
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
.detach_and_log_err(cx);
}
}
})
.into_any()
+17 -23
View File
@@ -6,11 +6,11 @@ use crate::{
use client::UserStore;
use gpui::{
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
ViewContext, ViewHandle,
ViewContext, ViewHandle, WeakViewHandle,
};
use picker::PickerEvent;
use project::Project;
use settings::Settings;
use workspace::Workspace;
actions!(contacts_popover, [ToggleContactFinder]);
@@ -29,23 +29,17 @@ enum Child {
pub struct ContactsPopover {
child: Child,
project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>,
workspace: WeakViewHandle<Workspace>,
_subscription: Option<gpui::Subscription>,
}
impl ContactsPopover {
pub fn new(
project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let mut this = Self {
child: Child::ContactList(
cx.add_view(|cx| ContactList::new(project.clone(), user_store.clone(), cx)),
),
project,
user_store,
child: Child::ContactList(cx.add_view(|cx| ContactList::new(workspace, cx))),
user_store: workspace.read(cx).user_store().clone(),
workspace: workspace.downgrade(),
_subscription: None,
};
this.show_contact_list(String::new(), cx);
@@ -74,16 +68,16 @@ impl ContactsPopover {
}
fn show_contact_list(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
let child = cx.add_view(|cx| {
ContactList::new(self.project.clone(), self.user_store.clone(), cx)
.with_editor_text(editor_text, cx)
});
cx.focus(&child);
self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed),
}));
self.child = Child::ContactList(child);
cx.notify();
if let Some(workspace) = self.workspace.upgrade(cx) {
let child = cx
.add_view(|cx| ContactList::new(&workspace, cx).with_editor_text(editor_text, cx));
cx.focus(&child);
self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed),
}));
self.child = Child::ContactList(child);
cx.notify();
}
}
}
@@ -1,3 +1,5 @@
use std::sync::{Arc, Weak};
use call::{ActiveCall, IncomingCall};
use client::proto;
use futures::StreamExt;
@@ -10,13 +12,14 @@ use gpui::{
};
use settings::Settings;
use util::ResultExt;
use workspace::JoinProject;
use workspace::AppState;
impl_internal_actions!(incoming_call_notification, [RespondToCall]);
pub fn init(cx: &mut AppContext) {
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
cx.add_action(IncomingCallNotification::respond_to_call);
let app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move {
let mut notification_windows = Vec::new();
@@ -48,7 +51,7 @@ pub fn init(cx: &mut AppContext) {
is_movable: false,
screen: Some(screen),
},
|_| IncomingCallNotification::new(incoming_call.clone()),
|_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
notification_windows.push(window_id);
@@ -66,11 +69,12 @@ struct RespondToCall {
pub struct IncomingCallNotification {
call: IncomingCall,
app_state: Weak<AppState>,
}
impl IncomingCallNotification {
pub fn new(call: IncomingCall) -> Self {
Self { call }
pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
Self { call, app_state }
}
fn respond_to_call(&mut self, action: &RespondToCall, cx: &mut ViewContext<Self>) {
@@ -79,15 +83,20 @@ impl IncomingCallNotification {
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
let caller_user_id = self.call.calling_user.id;
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
cx.spawn(|_, mut cx| async move {
cx.spawn(|this, mut cx| async move {
join.await?;
if let Some(project_id) = initial_project_id {
cx.update(|cx| {
cx.dispatch_global_action(JoinProject {
project_id,
follow_user_id: caller_user_id,
})
});
this.update(&mut cx, |this, cx| {
if let Some(app_state) = this.app_state.upgrade() {
workspace::join_remote_project(
project_id,
caller_user_id,
app_state,
cx,
)
.detach_and_log_err(cx);
}
})?;
}
anyhow::Ok(())
})
@@ -2,22 +2,17 @@ use call::{room, ActiveCall};
use client::User;
use collections::HashMap;
use gpui::{
actions,
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AppContext, Entity, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
use workspace::JoinProject;
actions!(project_shared_notification, [DismissProject]);
pub fn init(cx: &mut AppContext) {
cx.add_action(ProjectSharedNotification::join);
cx.add_action(ProjectSharedNotification::dismiss);
use std::sync::{Arc, Weak};
use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
let app_state = Arc::downgrade(app_state);
let active_call = ActiveCall::global(cx);
let mut notification_windows = HashMap::default();
cx.subscribe(&active_call, move |_, event, cx| match event {
@@ -50,6 +45,7 @@ pub fn init(cx: &mut AppContext) {
owner.clone(),
*project_id,
worktree_root_names.clone(),
app_state.clone(),
)
},
);
@@ -82,23 +78,33 @@ pub struct ProjectSharedNotification {
project_id: u64,
worktree_root_names: Vec<String>,
owner: Arc<User>,
app_state: Weak<AppState>,
}
impl ProjectSharedNotification {
fn new(owner: Arc<User>, project_id: u64, worktree_root_names: Vec<String>) -> Self {
fn new(
owner: Arc<User>,
project_id: u64,
worktree_root_names: Vec<String>,
app_state: Weak<AppState>,
) -> Self {
Self {
project_id,
worktree_root_names,
owner,
app_state,
}
}
fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
fn join(&mut self, cx: &mut ViewContext<Self>) {
cx.remove_window();
cx.propagate_action();
if let Some(app_state) = self.app_state.upgrade() {
workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
.detach_and_log_err(cx);
}
}
fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
cx.remove_window();
}
@@ -161,9 +167,6 @@ impl ProjectSharedNotification {
enum Open {}
enum Dismiss {}
let project_id = self.project_id;
let owner_user_id = self.owner.id;
Flex::column()
.with_child(
MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
@@ -174,12 +177,7 @@ impl ProjectSharedNotification {
.with_style(theme.open_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id,
follow_user_id: owner_user_id,
});
})
.on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
.flex(1., true),
)
.with_child(
@@ -191,8 +189,8 @@ impl ProjectSharedNotification {
.with_style(theme.dismiss_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, _, cx| {
cx.dispatch_action(DismissProject);
.on_click(MouseButton::Left, |_, this, cx| {
this.dismiss(cx);
})
.flex(1., true),
)
+27 -7
View File
@@ -404,11 +404,13 @@ mod tests {
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
sync::Arc,
};
use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext};
use project::{FakeFs, Project};
use settings::Settings;
use theme::ThemeRegistry;
use super::*;
use crate::{
@@ -419,7 +421,7 @@ mod tests {
},
register_deserializable_item,
sidebar::Sidebar,
ItemHandle, Workspace,
AppState, ItemHandle, Workspace,
};
pub fn default_item_factory(
@@ -467,8 +469,17 @@ mod tests {
Some(serialized_workspace),
0,
project.clone(),
default_item_factory,
|| &[],
Arc::new(AppState {
languages: project.read(cx).languages().clone(),
themes: ThemeRegistry::new((), cx.font_cache().clone()),
client: project.read(cx).client(),
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: default_item_factory,
background_actions: || &[],
}),
cx,
)
});
@@ -598,11 +609,20 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
None,
0,
project,
default_item_factory,
|| &[],
project.clone(),
Arc::new(AppState {
languages: project.read(cx).languages().clone(),
themes: ThemeRegistry::new((), cx.font_cache().clone()),
client: project.read(cx).client(),
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: default_item_factory,
background_actions: || &[],
}),
cx,
)
});
+2 -2
View File
@@ -365,7 +365,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
workspace.update_followers(
proto::update_followers::Variant::CreateView(proto::View {
id: followed_item
.remote_id(&workspace.client, cx)
.remote_id(&workspace.app_state.client, cx)
.map(|id| id.to_proto()),
variant: Some(message),
leader_id: workspace.leader_for_pane(&pane),
@@ -421,7 +421,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
proto::update_followers::Variant::UpdateView(
proto::UpdateView {
id: item
.remote_id(&this.client, cx)
.remote_id(&this.app_state.client, cx)
.map(|id| id.to_proto()),
variant: pending_update.borrow_mut().take(),
leader_id,
+27 -7
View File
@@ -1,4 +1,6 @@
use crate::{FollowerStatesByLeader, JoinProject, Pane, Workspace};
use std::sync::Arc;
use crate::{AppState, FollowerStatesByLeader, Pane, Workspace};
use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation};
use gpui::{
@@ -70,6 +72,7 @@ impl PaneGroup {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
self.root.render(
@@ -78,6 +81,7 @@ impl PaneGroup {
follower_states,
active_call,
active_pane,
app_state,
cx,
)
}
@@ -131,6 +135,7 @@ impl Member {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
enum FollowIntoExternalProject {}
@@ -175,6 +180,7 @@ impl Member {
} else {
let leader_user = leader.user.clone();
let leader_user_id = leader.user.id;
let app_state = Arc::downgrade(app_state);
Some(
MouseEventHandler::<FollowIntoExternalProject, _>::new(
pane.id(),
@@ -199,10 +205,15 @@ impl Member {
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, _, cx| {
cx.dispatch_action(JoinProject {
project_id: leader_project_id,
follow_user_id: leader_user_id,
})
if let Some(app_state) = app_state.upgrade() {
crate::join_remote_project(
leader_project_id,
leader_user_id,
app_state,
cx,
)
.detach_and_log_err(cx);
}
})
.aligned()
.bottom()
@@ -257,6 +268,7 @@ impl Member {
follower_states,
active_call,
active_pane,
app_state,
cx,
),
}
@@ -360,6 +372,7 @@ impl PaneAxis {
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
let last_member_ix = self.members.len() - 1;
@@ -370,8 +383,15 @@ impl PaneAxis {
flex = cx.global::<Settings>().active_pane_magnification;
}
let mut member =
member.render(project, theme, follower_state, active_call, active_pane, cx);
let mut member = member.render(
project,
theme,
follower_state,
active_call,
active_pane,
app_state,
cx,
);
if ix < last_member_ix {
let mut border = theme.workspace.pane_divider;
border.left = false;
+141 -83
View File
@@ -25,7 +25,6 @@ use client::{
use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockDefaultItemFactory, ToggleDockButton};
use drag_and_drop::DragAndDrop;
use fs::{self, Fs};
use futures::{
channel::{mpsc, oneshot},
future::try_join_all,
@@ -135,12 +134,6 @@ pub struct OpenPaths {
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
#[derive(Clone, PartialEq)]
pub struct JoinProject {
pub project_id: u64,
pub follow_user_id: u64,
}
#[derive(Clone, PartialEq)]
pub struct OpenSharedScreen {
pub peer_id: PeerId,
@@ -216,7 +209,6 @@ pub type WorkspaceId = i64;
impl_internal_actions!(
workspace,
[
JoinProject,
OpenSharedScreen,
RemoveWorktreeFromProject,
SplitWithItem,
@@ -524,10 +516,7 @@ pub enum Event {
pub struct Workspace {
weak_self: WeakViewHandle<Self>,
client: Arc<Client>,
user_store: ModelHandle<client::UserStore>,
remote_entity_subscription: Option<client::Subscription>,
fs: Arc<dyn Fs>,
modal: Option<AnyViewHandle>,
center: PaneGroup,
left_sidebar: ViewHandle<Sidebar>,
@@ -548,7 +537,7 @@ pub struct Workspace {
active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
background_actions: BackgroundActions,
app_state: Arc<AppState>,
_window_subscriptions: [Subscription; 3],
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<Result<()>>,
@@ -578,8 +567,7 @@ impl Workspace {
serialized_workspace: Option<SerializedWorkspace>,
workspace_id: WorkspaceId,
project: ModelHandle<Project>,
dock_default_factory: DockDefaultItemFactory,
background_actions: BackgroundActions,
app_state: Arc<AppState>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&project, |_, _, cx| cx.notify()).detach();
@@ -616,8 +604,8 @@ impl Workspace {
let weak_handle = cx.weak_handle();
let center_pane =
cx.add_view(|cx| Pane::new(weak_handle.clone(), None, background_actions, cx));
let center_pane = cx
.add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx));
let pane_id = center_pane.id();
cx.subscribe(&center_pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -625,14 +613,15 @@ impl Workspace {
.detach();
cx.focus(&center_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
let dock = Dock::new(dock_default_factory, background_actions, cx);
let dock = Dock::new(
app_state.dock_default_item_factory,
app_state.background_actions,
cx,
);
let dock_pane = dock.pane().clone();
let fs = project.read(cx).fs().clone();
let user_store = project.read(cx).user_store();
let client = project.read(cx).client();
let mut current_user = user_store.read(cx).watch_current_user();
let mut connection_status = client.status();
let mut current_user = app_state.user_store.read(cx).watch_current_user();
let mut connection_status = app_state.client.status();
let _observe_current_user = cx.spawn(|this, mut cx| async move {
current_user.recv().await;
connection_status.recv().await;
@@ -725,10 +714,7 @@ impl Workspace {
status_bar,
titlebar_item: None,
notifications: Default::default(),
client,
remote_entity_subscription: None,
user_store,
fs,
left_sidebar,
right_sidebar,
project: project.clone(),
@@ -738,7 +724,7 @@ impl Workspace {
window_edited: false,
active_call,
database_id: workspace_id,
background_actions,
app_state,
_observe_current_user,
_apply_leader_updates,
leader_updates_tx,
@@ -827,8 +813,7 @@ impl Workspace {
serialized_workspace,
workspace_id,
project_handle.clone(),
app_state.dock_default_item_factory,
app_state.background_actions,
app_state.clone(),
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
@@ -938,8 +923,12 @@ impl Workspace {
&self.status_bar
}
pub fn app_state(&self) -> &Arc<AppState> {
&self.app_state
}
pub fn user_store(&self) -> &ModelHandle<UserStore> {
&self.user_store
&self.app_state.user_store
}
pub fn project(&self) -> &ModelHandle<Project> {
@@ -947,7 +936,7 @@ impl Workspace {
}
pub fn client(&self) -> &Client {
&self.client
&self.app_state.client
}
pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
@@ -1183,7 +1172,7 @@ impl Workspace {
visible: bool,
cx: &mut ViewContext<Self>,
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
let fs = self.fs.clone();
let fs = self.app_state.fs.clone();
// Sort the paths to ensure we add worktrees for parents before their children.
abs_paths.sort_unstable();
@@ -1494,8 +1483,14 @@ impl Workspace {
}
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
let pane =
cx.add_view(|cx| Pane::new(self.weak_handle(), None, self.background_actions, cx));
let pane = cx.add_view(|cx| {
Pane::new(
self.weak_handle(),
None,
self.app_state.background_actions,
cx,
)
});
let pane_id = pane.id();
cx.subscribe(&pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -1688,7 +1683,7 @@ impl Workspace {
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
id: self.active_item(cx).and_then(|item| {
item.to_followable_item_handle(cx)?
.remote_id(&self.client, cx)
.remote_id(&self.app_state.client, cx)
.map(|id| id.to_proto())
}),
leader_id: self.leader_for_pane(&pane),
@@ -1857,8 +1852,11 @@ impl Workspace {
fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
if let Some(remote_id) = remote_id {
self.remote_entity_subscription =
Some(self.client.add_view_for_remote_entity(remote_id, cx));
self.remote_entity_subscription = Some(
self.app_state
.client
.add_view_for_remote_entity(remote_id, cx),
);
} else {
self.remote_entity_subscription.take();
}
@@ -1898,7 +1896,7 @@ impl Workspace {
cx.notify();
let project_id = self.project.read(cx).remote_id()?;
let request = self.client.request(proto::Follow {
let request = self.app_state.client.request(proto::Follow {
project_id,
leader_id: Some(leader_id),
});
@@ -1977,7 +1975,8 @@ impl Workspace {
if states_by_pane.is_empty() {
self.follower_states_by_leader.remove(&leader_id);
if let Some(project_id) = self.project.read(cx).remote_id() {
self.client
self.app_state
.client
.send(proto::Unfollow {
project_id,
leader_id: Some(leader_id),
@@ -2158,7 +2157,7 @@ impl Workspace {
mut cx: AsyncAppContext,
) -> Result<proto::FollowResponse> {
this.update(&mut cx, |this, cx| {
let client = &this.client;
let client = &this.app_state.client;
this.leader_state
.followers
.insert(envelope.original_sender_id()?);
@@ -2366,7 +2365,8 @@ impl Workspace {
) -> Option<()> {
let project_id = self.project.read(cx).remote_id()?;
if !self.leader_state.followers.is_empty() {
self.client
self.app_state
.client
.send(proto::UpdateFollowers {
project_id,
follower_ids: self.leader_state.followers.iter().copied().collect(),
@@ -2698,7 +2698,18 @@ impl Workspace {
#[cfg(any(test, feature = "test-support"))]
pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
Self::new(None, 0, project, |_, _| None, || &[], cx)
let app_state = Arc::new(AppState {
languages: project.read(cx).languages().clone(),
themes: ThemeRegistry::new((), cx.font_cache().clone()),
client: project.read(cx).client(),
user_store: project.read(cx).user_store(),
fs: project.read(cx).fs().clone(),
build_window_options: |_, _, _| Default::default(),
initialize_workspace: |_, _, _| {},
dock_default_item_factory: |_, _| None,
background_actions: || &[],
});
Self::new(None, 0, project, app_state, cx)
}
}
@@ -2784,6 +2795,7 @@ impl View for Workspace {
&self.follower_states_by_leader,
self.active_call(),
self.active_pane(),
&self.app_state,
cx,
))
.flex(1., true),
@@ -3015,6 +3027,87 @@ pub fn open_new(
})
}
pub fn join_remote_project(
project_id: u64,
follow_user_id: u64,
app_state: Arc<AppState>,
cx: &mut AppContext,
) -> Task<Result<()>> {
cx.spawn(|mut cx| async move {
let existing_workspace = cx.update(|cx| {
cx.window_ids()
.filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
.find(|workspace| {
workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
})
});
let workspace = if let Some(existing_workspace) = existing_workspace {
existing_workspace.downgrade()
} else {
let active_call = cx.read(ActiveCall::global);
let room = active_call
.read_with(&cx, |call, _| call.room().cloned())
.ok_or_else(|| anyhow!("not in a call"))?;
let project = room
.update(&mut cx, |room, cx| {
room.join_project(
project_id,
app_state.languages.clone(),
app_state.fs.clone(),
cx,
)
})
.await?;
let (_, workspace) = cx.add_window(
(app_state.build_window_options)(None, None, cx.platform().as_ref()),
|cx| {
let mut workspace =
Workspace::new(Default::default(), 0, project, app_state.clone(), cx);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
workspace
},
);
workspace.downgrade()
};
cx.activate_window(workspace.window_id());
cx.platform().activate(true);
workspace.update(&mut cx, |workspace, cx| {
if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
let follow_peer_id = room
.read(cx)
.remote_participants()
.iter()
.find(|(_, participant)| participant.user.id == follow_user_id)
.map(|(_, p)| p.peer_id)
.or_else(|| {
// If we couldn't follow the given user, follow the host instead.
let collaborator = workspace
.project()
.read(cx)
.collaborators()
.values()
.find(|collaborator| collaborator.replica_id == 0)?;
Some(collaborator.peer_id)
});
if let Some(follow_peer_id) = follow_peer_id {
if !workspace.is_being_followed(follow_peer_id) {
workspace
.toggle_follow(follow_peer_id, cx)
.map(|follow| follow.detach_and_log_err(cx));
}
}
}
})?;
anyhow::Ok(())
})
}
fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
let mut parts = value.split(',');
let width: usize = parts.next()?.parse().ok()?;
@@ -3041,16 +3134,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
0,
project.clone(),
|_, _| None,
|| &[],
cx,
)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Adding an item with no ambiguity renders the tab without detail.
let item1 = cx.add_view(&workspace, |_| {
@@ -3114,16 +3198,7 @@ mod tests {
.await;
let project = Project::test(fs, ["root1".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
0,
project.clone(),
|_, _| None,
|| &[],
cx,
)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
@@ -3213,16 +3288,7 @@ mod tests {
fs.insert_tree("/root", json!({ "one": "" })).await;
let project = Project::test(fs, ["root".as_ref()], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(
Default::default(),
0,
project.clone(),
|_, _| None,
|| &[],
cx,
)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// When there are no dirty items, there's nothing to do.
let item1 = cx.add_view(&workspace, |_| TestItem::new());
@@ -3257,9 +3323,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item1 = cx.add_view(&workspace, |cx| {
TestItem::new()
@@ -3366,9 +3430,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Create several workspace items with single project entries, and two
// workspace items with multiple project entries.
@@ -3475,9 +3537,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item = cx.add_view(&workspace, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -3594,9 +3654,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
});
let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item = cx.add_view(&workspace, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+1 -1
View File
@@ -199,7 +199,7 @@ fn main() {
language_selector::init(app_state.clone(), cx);
theme_selector::init(app_state.clone(), cx);
zed::init(&app_state, cx);
collab_ui::init(app_state.clone(), cx);
collab_ui::init(&app_state, cx);
feedback::init(app_state.clone(), cx);
welcome::init(cx);
+1 -2
View File
@@ -302,8 +302,7 @@ pub fn initialize_workspace(
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone()));
let collab_titlebar_item =
cx.add_view(|cx| CollabTitlebarItem::new(&workspace_handle, &app_state.user_store, cx));
let collab_titlebar_item = cx.add_view(|cx| CollabTitlebarItem::new(&workspace_handle, cx));
workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);