Detailed changes
@@ -1,7 +1,7 @@
use super::{http::HttpClient, proto, Client, Status, TypedEnvelope};
use crate::incoming_call::IncomingCall;
use anyhow::{anyhow, Context, Result};
-use collections::{hash_map::Entry, BTreeSet, HashMap, HashSet};
+use collections::{hash_map::Entry, HashMap, HashSet};
use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
use gpui::{AsyncAppContext, Entity, ImageData, ModelContext, ModelHandle, Task};
use postage::{sink::Sink, watch};
@@ -40,14 +40,6 @@ impl Eq for User {}
pub struct Contact {
pub user: Arc<User>,
pub online: bool,
- pub projects: Vec<ProjectMetadata>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct ProjectMetadata {
- pub id: u64,
- pub visible_worktree_root_names: Vec<String>,
- pub guests: BTreeSet<Arc<User>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -290,7 +282,6 @@ impl UserStore {
let mut user_ids = HashSet::default();
for contact in &message.contacts {
user_ids.insert(contact.user_id);
- user_ids.extend(contact.projects.iter().flat_map(|w| &w.guests).copied());
}
user_ids.extend(message.incoming_requests.iter().map(|req| req.requester_id));
user_ids.extend(message.outgoing_requests.iter());
@@ -688,34 +679,11 @@ impl Contact {
user_store.get_user(contact.user_id, cx)
})
.await?;
- let mut projects = Vec::new();
- for project in contact.projects {
- let mut guests = BTreeSet::new();
- for participant_id in project.guests {
- guests.insert(
- user_store
- .update(cx, |user_store, cx| user_store.get_user(participant_id, cx))
- .await?,
- );
- }
- projects.push(ProjectMetadata {
- id: project.id,
- visible_worktree_root_names: project.visible_worktree_root_names.clone(),
- guests,
- });
- }
Ok(Self {
user,
online: contact.online,
- projects,
})
}
-
- pub fn non_empty_projects(&self) -> impl Iterator<Item = &ProjectMetadata> {
- self.projects
- .iter()
- .filter(|project| !project.visible_worktree_root_names.is_empty())
- }
}
async fn fetch_avatar(http: &dyn HttpClient, url: &str) -> Result<Arc<ImageData>> {
@@ -8,7 +8,7 @@ use anyhow::anyhow;
use call::Room;
use client::{
self, proto, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
- Credentials, EstablishConnectionError, ProjectMetadata, UserStore, RECEIVE_TIMEOUT,
+ Credentials, EstablishConnectionError, UserStore, RECEIVE_TIMEOUT,
};
use collections::{BTreeMap, HashMap, HashSet};
use editor::{
@@ -731,294 +731,6 @@ async fn test_cancel_join_request(
);
}
-#[gpui::test(iterations = 10)]
-async fn test_offline_projects(
- deterministic: Arc<Deterministic>,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
- cx_c: &mut TestAppContext,
-) {
- cx_a.foreground().forbid_parking();
- let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- let client_c = server.create_client(cx_c, "user_c").await;
- let user_a = UserId::from_proto(client_a.user_id().unwrap());
- server
- .make_contacts(vec![
- (&client_a, cx_a),
- (&client_b, cx_b),
- (&client_c, cx_c),
- ])
- .await;
-
- // Set up observers of the project and user stores. Any time either of
- // these models update, they should be in a consistent state with each
- // other. There should not be an observable moment where the current
- // user's contact entry contains a project that does not match one of
- // the current open projects. That would cause a duplicate entry to be
- // shown in the contacts panel.
- let mut subscriptions = vec![];
- let (window_id, view) = cx_a.add_window(|cx| {
- subscriptions.push(cx.observe(&client_a.user_store, {
- let project_store = client_a.project_store.clone();
- let user_store = client_a.user_store.clone();
- move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
- }));
-
- subscriptions.push(cx.observe(&client_a.project_store, {
- let project_store = client_a.project_store.clone();
- let user_store = client_a.user_store.clone();
- move |_, _, cx| check_project_list(project_store.clone(), user_store.clone(), cx)
- }));
-
- fn check_project_list(
- project_store: ModelHandle<ProjectStore>,
- user_store: ModelHandle<UserStore>,
- cx: &mut gpui::MutableAppContext,
- ) {
- let user_store = user_store.read(cx);
- for contact in user_store.contacts() {
- if contact.user.id == user_store.current_user().unwrap().id {
- for project in &contact.projects {
- let store_contains_project = project_store
- .read(cx)
- .projects(cx)
- .filter_map(|project| project.read(cx).remote_id())
- .any(|x| x == project.id);
-
- if !store_contains_project {
- panic!(
- concat!(
- "current user's contact data has a project",
- "that doesn't match any open project {:?}",
- ),
- project
- );
- }
- }
- }
- }
- }
-
- EmptyView
- });
-
- // Build an offline project with two worktrees.
- client_a
- .fs
- .insert_tree(
- "/code",
- json!({
- "crate1": { "a.rs": "" },
- "crate2": { "b.rs": "" },
- }),
- )
- .await;
- let project = cx_a.update(|cx| {
- Project::local(
- false,
- client_a.client.clone(),
- client_a.user_store.clone(),
- client_a.project_store.clone(),
- client_a.language_registry.clone(),
- client_a.fs.clone(),
- cx,
- )
- });
- project
- .update(cx_a, |p, cx| {
- p.find_or_create_local_worktree("/code/crate1", true, cx)
- })
- .await
- .unwrap();
- project
- .update(cx_a, |p, cx| {
- p.find_or_create_local_worktree("/code/crate2", true, cx)
- })
- .await
- .unwrap();
- project
- .update(cx_a, |p, cx| p.restore_state(cx))
- .await
- .unwrap();
-
- // When a project is offline, we still create it on the server but is invisible
- // to other users.
- deterministic.run_until_parked();
- assert!(server
- .store
- .lock()
- .await
- .project_metadata_for_user(user_a)
- .is_empty());
- project.read_with(cx_a, |project, _| {
- assert!(project.remote_id().is_some());
- assert!(!project.is_online());
- });
- assert!(client_b
- .user_store
- .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
-
- // When the project is taken online, its metadata is sent to the server
- // and broadcasted to other users.
- project.update(cx_a, |p, cx| p.set_online(true, cx));
- deterministic.run_until_parked();
- let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
- client_b.user_store.read_with(cx_b, |store, _| {
- assert_eq!(
- store.contacts()[0].projects,
- &[ProjectMetadata {
- id: project_id,
- visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
- guests: Default::default(),
- }]
- );
- });
-
- // The project is registered again when the host loses and regains connection.
- server.disconnect_client(user_a);
- server.forbid_connections();
- cx_a.foreground().advance_clock(rpc::RECEIVE_TIMEOUT);
- assert!(server
- .store
- .lock()
- .await
- .project_metadata_for_user(user_a)
- .is_empty());
- assert!(project.read_with(cx_a, |p, _| p.remote_id().is_none()));
- assert!(client_b
- .user_store
- .read_with(cx_b, |store, _| { store.contacts()[0].projects.is_empty() }));
-
- server.allow_connections();
- cx_b.foreground().advance_clock(Duration::from_secs(10));
- let project_id = project.read_with(cx_a, |p, _| p.remote_id()).unwrap();
- client_b.user_store.read_with(cx_b, |store, _| {
- assert_eq!(
- store.contacts()[0].projects,
- &[ProjectMetadata {
- id: project_id,
- visible_worktree_root_names: vec!["crate1".into(), "crate2".into()],
- guests: Default::default(),
- }]
- );
- });
-
- project
- .update(cx_a, |p, cx| {
- p.find_or_create_local_worktree("/code/crate3", true, cx)
- })
- .await
- .unwrap();
- deterministic.run_until_parked();
- client_b.user_store.read_with(cx_b, |store, _| {
- assert_eq!(
- store.contacts()[0].projects,
- &[ProjectMetadata {
- id: project_id,
- visible_worktree_root_names: vec![
- "crate1".into(),
- "crate2".into(),
- "crate3".into()
- ],
- guests: Default::default(),
- }]
- );
- });
-
- // Build another project using a directory which was previously part of
- // an online project. Restore the project's state from the host's database.
- let project2_a = cx_a.update(|cx| {
- Project::local(
- false,
- client_a.client.clone(),
- client_a.user_store.clone(),
- client_a.project_store.clone(),
- client_a.language_registry.clone(),
- client_a.fs.clone(),
- cx,
- )
- });
- project2_a
- .update(cx_a, |p, cx| {
- p.find_or_create_local_worktree("/code/crate3", true, cx)
- })
- .await
- .unwrap();
- project2_a
- .update(cx_a, |project, cx| project.restore_state(cx))
- .await
- .unwrap();
-
- // This project is now online, because its directory was previously online.
- project2_a.read_with(cx_a, |project, _| assert!(project.is_online()));
- deterministic.run_until_parked();
- let project2_id = project2_a.read_with(cx_a, |p, _| p.remote_id()).unwrap();
- client_b.user_store.read_with(cx_b, |store, _| {
- assert_eq!(
- store.contacts()[0].projects,
- &[
- ProjectMetadata {
- id: project_id,
- visible_worktree_root_names: vec![
- "crate1".into(),
- "crate2".into(),
- "crate3".into()
- ],
- guests: Default::default(),
- },
- ProjectMetadata {
- id: project2_id,
- visible_worktree_root_names: vec!["crate3".into()],
- guests: Default::default(),
- }
- ]
- );
- });
-
- let project2_b = client_b.build_remote_project(&project2_a, cx_a, cx_b).await;
- let project2_c = cx_c.foreground().spawn(Project::remote(
- project2_id,
- client_c.client.clone(),
- client_c.user_store.clone(),
- client_c.project_store.clone(),
- client_c.language_registry.clone(),
- FakeFs::new(cx_c.background()),
- cx_c.to_async(),
- ));
- deterministic.run_until_parked();
-
- // Taking a project offline unshares the project, rejects any pending join request and
- // disconnects existing guests.
- project2_a.update(cx_a, |project, cx| project.set_online(false, cx));
- deterministic.run_until_parked();
- project2_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
- project2_b.read_with(cx_b, |project, _| assert!(project.is_read_only()));
- project2_c.await.unwrap_err();
-
- client_b.user_store.read_with(cx_b, |store, _| {
- assert_eq!(
- store.contacts()[0].projects,
- &[ProjectMetadata {
- id: project_id,
- visible_worktree_root_names: vec![
- "crate1".into(),
- "crate2".into(),
- "crate3".into()
- ],
- guests: Default::default(),
- },]
- );
- });
-
- cx_a.update(|cx| {
- drop(subscriptions);
- drop(view);
- cx.remove_window(window_id);
- });
-}
-
#[gpui::test(iterations = 10)]
async fn test_propagate_saves_and_fs_changes(
cx_a: &mut TestAppContext,
@@ -3911,24 +3623,15 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_b".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_b".to_string(), true)]
);
// Share a project as client A.
@@ -3938,24 +3641,15 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- ("user_a".to_string(), true, vec![("a".to_string(), vec![])]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- ("user_a".to_string(), true, vec![("a".to_string(), vec![])]),
- ("user_b".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_b".to_string(), true)]
);
let _project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
@@ -3963,32 +3657,15 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- (
- "user_a".to_string(),
- true,
- vec![("a".to_string(), vec!["user_b".to_string()])]
- ),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true,), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- (
- "user_a".to_string(),
- true,
- vec![("a".to_string(), vec!["user_b".to_string()])]
- ),
- ("user_b".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true,), ("user_b".to_string(), true)]
);
// Add a local project as client B
@@ -3998,32 +3675,15 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- (
- "user_a".to_string(),
- true,
- vec![("a".to_string(), vec!["user_b".to_string()])]
- ),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- (
- "user_a".to_string(),
- true,
- vec![("a".to_string(), vec!["user_b".to_string()])]
- ),
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
- ]
+ [("user_a".to_string(), true,), ("user_b".to_string(), true)]
);
project_a
@@ -4036,24 +3696,15 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
- ]
+ [("user_a".to_string(), true), ("user_b".to_string(), true)]
);
server.disconnect_client(client_c.current_user_id(cx_c));
@@ -4061,17 +3712,11 @@ async fn test_contacts(
deterministic.advance_clock(rpc::RECEIVE_TIMEOUT);
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
- ("user_c".to_string(), false, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), false)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_c".to_string(), false, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), false)]
);
assert_eq!(contacts(&client_c, cx_c), []);
@@ -4084,48 +3729,24 @@ async fn test_contacts(
deterministic.run_until_parked();
assert_eq!(
contacts(&client_a, cx_a),
- [
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_b".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_b, cx_b),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_c".to_string(), true, vec![])
- ]
+ [("user_a".to_string(), true), ("user_c".to_string(), true)]
);
assert_eq!(
contacts(&client_c, cx_c),
- [
- ("user_a".to_string(), true, vec![]),
- ("user_b".to_string(), true, vec![("b".to_string(), vec![])])
- ]
+ [("user_a".to_string(), true), ("user_b".to_string(), true)]
);
#[allow(clippy::type_complexity)]
- fn contacts(
- client: &TestClient,
- cx: &TestAppContext,
- ) -> Vec<(String, bool, Vec<(String, Vec<String>)>)> {
+ fn contacts(client: &TestClient, cx: &TestAppContext) -> Vec<(String, bool)> {
client.user_store.read_with(cx, |store, _| {
store
.contacts()
.iter()
- .map(|contact| {
- let projects = contact
- .projects
- .iter()
- .map(|p| {
- (
- p.visible_worktree_root_names[0].clone(),
- p.guests.iter().map(|p| p.github_login.clone()).collect(),
- )
- })
- .collect();
- (contact.user.github_login.clone(), contact.online, projects)
- })
+ .map(|contact| (contact.user.github_login.clone(), contact.online))
.collect()
})
}
@@ -5155,22 +4776,6 @@ async fn test_random_collaboration(
log::error!("{} error - {:?}", guest.username, guest_err);
}
- let contacts = server
- .app_state
- .db
- .get_contacts(guest.current_user_id(&guest_cx))
- .await
- .unwrap();
- let contacts = server
- .store
- .lock()
- .await
- .build_initial_contacts_update(contacts)
- .contacts;
- assert!(!contacts
- .iter()
- .flat_map(|contact| &contact.projects)
- .any(|project| project.id == host_project_id));
guest_project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
guest_cx.update(|_| drop((guest, guest_project)));
}
@@ -5259,14 +4864,6 @@ async fn test_random_collaboration(
"removed guest is still a contact of another peer"
);
}
- for project in contact.projects {
- for project_guest_id in project.guests {
- assert_ne!(
- project_guest_id, removed_guest_id.0 as u64,
- "removed guest appears as still participating on a project"
- );
- }
- }
}
}
@@ -345,47 +345,11 @@ impl Store {
pub fn contact_for_user(&self, user_id: UserId, should_notify: bool) -> proto::Contact {
proto::Contact {
user_id: user_id.to_proto(),
- projects: self.project_metadata_for_user(user_id),
online: self.is_user_online(user_id),
should_notify,
}
}
- pub fn project_metadata_for_user(&self, user_id: UserId) -> Vec<proto::ProjectMetadata> {
- let user_connection_state = self.connected_users.get(&user_id);
- let project_ids = user_connection_state.iter().flat_map(|state| {
- state
- .connection_ids
- .iter()
- .filter_map(|connection_id| self.connections.get(connection_id))
- .flat_map(|connection| connection.projects.iter().copied())
- });
-
- let mut metadata = Vec::new();
- for project_id in project_ids {
- if let Some(project) = self.projects.get(&project_id) {
- if project.host.user_id == user_id && project.online {
- metadata.push(proto::ProjectMetadata {
- id: project_id.to_proto(),
- visible_worktree_root_names: project
- .worktrees
- .values()
- .filter(|worktree| worktree.visible)
- .map(|worktree| worktree.root_name.clone())
- .collect(),
- guests: project
- .guests
- .values()
- .map(|guest| guest.user_id.to_proto())
- .collect(),
- });
- }
- }
- }
-
- metadata
- }
-
pub fn create_room(&mut self, creator_connection_id: ConnectionId) -> Result<RoomId> {
let connection = self
.connections
@@ -8,23 +8,19 @@ use contact_notification::ContactNotification;
use editor::{Cancel, Editor};
use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
- actions,
- elements::*,
- geometry::{rect::RectF, vector::vec2f},
- impl_actions, impl_internal_actions,
- platform::CursorStyle,
+ actions, elements::*, impl_actions, impl_internal_actions, platform::CursorStyle,
AnyViewHandle, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle,
MouseButton, MutableAppContext, RenderContext, Subscription, View, ViewContext, ViewHandle,
- WeakModelHandle, WeakViewHandle,
+ WeakViewHandle,
};
use join_project_notification::JoinProjectNotification;
use menu::{Confirm, SelectNext, SelectPrev};
-use project::{Project, ProjectStore};
+use project::ProjectStore;
use serde::Deserialize;
use settings::Settings;
-use std::{ops::DerefMut, sync::Arc};
+use std::sync::Arc;
use theme::IconButton;
-use workspace::{sidebar::SidebarItem, JoinProject, ToggleProjectOnline, Workspace};
+use workspace::{sidebar::SidebarItem, Workspace};
actions!(contacts_panel, [ToggleFocus]);
@@ -48,8 +44,6 @@ enum ContactEntry {
IncomingRequest(Arc<User>),
OutgoingRequest(Arc<User>),
Contact(Arc<Contact>),
- ContactProject(Arc<Contact>, usize, Option<WeakModelHandle<Project>>),
- OfflineProject(WeakModelHandle<Project>),
}
#[derive(Clone, PartialEq)]
@@ -181,7 +175,6 @@ impl ContactsPanel {
let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
let theme = cx.global::<Settings>().theme.clone();
- let current_user_id = this.user_store.read(cx).current_user().map(|user| user.id);
let is_selected = this.selection == Some(ix);
match &this.entries[ix] {
@@ -214,34 +207,6 @@ impl ContactsPanel {
ContactEntry::Contact(contact) => {
Self::render_contact(&contact.user, &theme.contacts_panel, is_selected)
}
- ContactEntry::ContactProject(contact, project_ix, open_project) => {
- let is_last_project_for_contact =
- this.entries.get(ix + 1).map_or(true, |next| {
- if let ContactEntry::ContactProject(next_contact, _, _) = next {
- next_contact.user.id != contact.user.id
- } else {
- true
- }
- });
- Self::render_project(
- contact.clone(),
- current_user_id,
- *project_ix,
- *open_project,
- &theme.contacts_panel,
- &theme.tooltip,
- is_last_project_for_contact,
- is_selected,
- cx,
- )
- }
- ContactEntry::OfflineProject(project) => Self::render_offline_project(
- *project,
- &theme.contacts_panel,
- &theme.tooltip,
- is_selected,
- cx,
- ),
}
});
@@ -343,260 +308,6 @@ impl ContactsPanel {
.boxed()
}
- #[allow(clippy::too_many_arguments)]
- fn render_project(
- contact: Arc<Contact>,
- current_user_id: Option<u64>,
- project_index: usize,
- open_project: Option<WeakModelHandle<Project>>,
- theme: &theme::ContactsPanel,
- tooltip_style: &TooltipStyle,
- is_last_project: bool,
- is_selected: bool,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
- enum ToggleOnline {}
-
- let project = &contact.projects[project_index];
- let project_id = project.id;
- let is_host = Some(contact.user.id) == current_user_id;
- let open_project = open_project.and_then(|p| p.upgrade(cx.deref_mut()));
-
- let font_cache = cx.font_cache();
- let host_avatar_height = theme
- .contact_avatar
- .width
- .or(theme.contact_avatar.height)
- .unwrap_or(0.);
- let row = &theme.project_row.default;
- let tree_branch = theme.tree_branch;
- let line_height = row.name.text.line_height(font_cache);
- let cap_height = row.name.text.cap_height(font_cache);
- let baseline_offset =
- row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
-
- MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, cx| {
- let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
- let row = theme.project_row.style_for(mouse_state, is_selected);
-
- Flex::row()
- .with_child(
- Stack::new()
- .with_child(
- Canvas::new(move |bounds, _, cx| {
- let start_x = bounds.min_x() + (bounds.width() / 2.)
- - (tree_branch.width / 2.);
- let end_x = bounds.max_x();
- let start_y = bounds.min_y();
- let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
-
- cx.scene.push_quad(gpui::Quad {
- bounds: RectF::from_points(
- vec2f(start_x, start_y),
- vec2f(
- start_x + tree_branch.width,
- if is_last_project {
- end_y
- } else {
- bounds.max_y()
- },
- ),
- ),
- background: Some(tree_branch.color),
- border: gpui::Border::default(),
- corner_radius: 0.,
- });
- cx.scene.push_quad(gpui::Quad {
- bounds: RectF::from_points(
- vec2f(start_x, end_y),
- vec2f(end_x, end_y + tree_branch.width),
- ),
- background: Some(tree_branch.color),
- border: gpui::Border::default(),
- corner_radius: 0.,
- });
- })
- .boxed(),
- )
- .with_children(open_project.and_then(|open_project| {
- let is_going_offline = !open_project.read(cx).is_online();
- if !mouse_state.hovered && !is_going_offline {
- return None;
- }
-
- let button = MouseEventHandler::<ToggleProjectOnline>::new(
- project_id as usize,
- cx,
- |state, _| {
- let mut icon_style =
- *theme.private_button.style_for(state, false);
- icon_style.container.background_color =
- row.container.background_color;
- if is_going_offline {
- icon_style.color = theme.disabled_button.color;
- }
- render_icon_button(&icon_style, "icons/lock_8.svg")
- .aligned()
- .boxed()
- },
- );
-
- if is_going_offline {
- Some(button.boxed())
- } else {
- Some(
- button
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleProjectOnline {
- project: Some(open_project.clone()),
- })
- })
- .with_tooltip::<ToggleOnline, _>(
- project_id as usize,
- "Take project offline".to_string(),
- None,
- tooltip_style.clone(),
- cx,
- )
- .boxed(),
- )
- }
- }))
- .constrained()
- .with_width(host_avatar_height)
- .boxed(),
- )
- .with_child(
- Label::new(
- project.visible_worktree_root_names.join(", "),
- row.name.text.clone(),
- )
- .aligned()
- .left()
- .contained()
- .with_style(row.name.container)
- .flex(1., false)
- .boxed(),
- )
- .with_children(project.guests.iter().filter_map(|participant| {
- participant.avatar.clone().map(|avatar| {
- Image::new(avatar)
- .with_style(row.guest_avatar)
- .aligned()
- .left()
- .contained()
- .with_margin_right(row.guest_avatar_spacing)
- .boxed()
- })
- }))
- .constrained()
- .with_height(theme.row_height)
- .contained()
- .with_style(row.container)
- .boxed()
- })
- .with_cursor_style(if !is_host {
- CursorStyle::PointingHand
- } else {
- CursorStyle::Arrow
- })
- .on_click(MouseButton::Left, move |_, cx| {
- if !is_host {
- cx.dispatch_global_action(JoinProject {
- contact: contact.clone(),
- project_index,
- });
- }
- })
- .boxed()
- }
-
- fn render_offline_project(
- project_handle: WeakModelHandle<Project>,
- theme: &theme::ContactsPanel,
- tooltip_style: &TooltipStyle,
- is_selected: bool,
- cx: &mut RenderContext<Self>,
- ) -> ElementBox {
- let host_avatar_height = theme
- .contact_avatar
- .width
- .or(theme.contact_avatar.height)
- .unwrap_or(0.);
-
- enum LocalProject {}
- enum ToggleOnline {}
-
- let project_id = project_handle.id();
- MouseEventHandler::<LocalProject>::new(project_id, cx, |state, cx| {
- let row = theme.project_row.style_for(state, is_selected);
- let mut worktree_root_names = String::new();
- let project = if let Some(project) = project_handle.upgrade(cx.deref_mut()) {
- project.read(cx)
- } else {
- return Empty::new().boxed();
- };
- let is_going_online = project.is_online();
- for tree in project.visible_worktrees(cx) {
- if !worktree_root_names.is_empty() {
- worktree_root_names.push_str(", ");
- }
- worktree_root_names.push_str(tree.read(cx).root_name());
- }
-
- Flex::row()
- .with_child({
- let button =
- MouseEventHandler::<ToggleOnline>::new(project_id, cx, |state, _| {
- let mut style = *theme.private_button.style_for(state, false);
- if is_going_online {
- style.color = theme.disabled_button.color;
- }
- render_icon_button(&style, "icons/lock_8.svg")
- .aligned()
- .constrained()
- .with_width(host_avatar_height)
- .boxed()
- });
-
- if is_going_online {
- button.boxed()
- } else {
- button
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
- let project = project_handle.upgrade(cx.app);
- cx.dispatch_action(ToggleProjectOnline { project })
- })
- .with_tooltip::<ToggleOnline, _>(
- project_id,
- "Take project online".to_string(),
- None,
- tooltip_style.clone(),
- cx,
- )
- .boxed()
- }
- })
- .with_child(
- Label::new(worktree_root_names, row.name.text.clone())
- .aligned()
- .left()
- .contained()
- .with_style(row.name.container)
- .flex(1., false)
- .boxed(),
- )
- .constrained()
- .with_height(theme.row_height)
- .contained()
- .with_style(row.container)
- .boxed()
- })
- .boxed()
- }
-
fn render_contact_request(
user: Arc<User>,
user_store: ModelHandle<UserStore>,
@@ -710,7 +421,6 @@ impl ContactsPanel {
fn update_entries(&mut self, cx: &mut ViewContext<Self>) {
let user_store = self.user_store.read(cx);
- let project_store = self.project_store.read(cx);
let query = self.filter_editor.read(cx).text(cx);
let executor = cx.background().clone();
@@ -837,60 +547,6 @@ impl ContactsPanel {
for mat in matches {
let contact = &contacts[mat.candidate_id];
self.entries.push(ContactEntry::Contact(contact.clone()));
-
- let is_current_user = current_user
- .as_ref()
- .map_or(false, |user| user.id == contact.user.id);
- if is_current_user {
- let mut open_projects =
- project_store.projects(cx).collect::<Vec<_>>();
- self.entries.extend(
- contact.projects.iter().enumerate().filter_map(
- |(ix, project)| {
- let open_project = open_projects
- .iter()
- .position(|p| {
- p.read(cx).remote_id() == Some(project.id)
- })
- .map(|ix| open_projects.remove(ix).downgrade());
- if project.visible_worktree_root_names.is_empty() {
- None
- } else {
- Some(ContactEntry::ContactProject(
- contact.clone(),
- ix,
- open_project,
- ))
- }
- },
- ),
- );
- self.entries.extend(open_projects.into_iter().filter_map(
- |project| {
- if project.read(cx).visible_worktrees(cx).next().is_none() {
- None
- } else {
- Some(ContactEntry::OfflineProject(project.downgrade()))
- }
- },
- ));
- } else {
- self.entries.extend(
- contact.projects.iter().enumerate().filter_map(
- |(ix, project)| {
- if project.visible_worktree_root_names.is_empty() {
- None
- } else {
- Some(ContactEntry::ContactProject(
- contact.clone(),
- ix,
- None,
- ))
- }
- },
- ),
- );
- }
}
}
}
@@ -981,18 +637,6 @@ impl ContactsPanel {
let section = *section;
self.toggle_expanded(&ToggleExpanded(section), cx);
}
- ContactEntry::ContactProject(contact, project_index, open_project) => {
- if let Some(open_project) = open_project {
- workspace::activate_workspace_for_project(cx, |_, cx| {
- cx.model_id() == open_project.id()
- });
- } else {
- cx.dispatch_global_action(JoinProject {
- contact: contact.clone(),
- project_index: *project_index,
- })
- }
- }
_ => {}
}
}
@@ -1181,16 +825,6 @@ impl PartialEq for ContactEntry {
return contact_1.user.id == contact_2.user.id;
}
}
- ContactEntry::ContactProject(contact_1, ix_1, _) => {
- if let ContactEntry::ContactProject(contact_2, ix_2, _) = other {
- return contact_1.user.id == contact_2.user.id && ix_1 == ix_2;
- }
- }
- ContactEntry::OfflineProject(project_1) => {
- if let ContactEntry::OfflineProject(project_2) = other {
- return project_1.id() == project_2.id();
- }
- }
}
false
}
@@ -1205,7 +839,7 @@ mod tests {
Client,
};
use collections::HashSet;
- use gpui::{serde_json::json, TestAppContext};
+ use gpui::TestAppContext;
use language::LanguageRegistry;
use project::{FakeFs, Project};
@@ -1221,8 +855,6 @@ mod tests {
let project_store = cx.add_model(|_| ProjectStore::new(project::Db::open_fake()));
let server = FakeServer::for_client(current_user_id, &client, cx).await;
let fs = FakeFs::new(cx.background());
- fs.insert_tree("/private_dir", json!({ "one.rs": "" }))
- .await;
let project = cx.update(|cx| {
Project::local(
false,
@@ -1234,14 +866,6 @@ mod tests {
cx,
)
});
- let worktree_id = project
- .update(cx, |project, cx| {
- project.find_or_create_local_worktree("/private_dir", true, cx)
- })
- .await
- .unwrap()
- .0
- .read_with(cx, |worktree, _| worktree.id().to_proto());
let (_, workspace) =
cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx));
@@ -1315,211 +939,26 @@ mod tests {
user_id: 3,
online: true,
should_notify: false,
- projects: vec![proto::ProjectMetadata {
- id: 101,
- visible_worktree_root_names: vec!["dir1".to_string()],
- guests: vec![2],
- }],
},
proto::Contact {
user_id: 4,
online: true,
should_notify: false,
- projects: vec![proto::ProjectMetadata {
- id: 102,
- visible_worktree_root_names: vec!["dir2".to_string()],
- guests: vec![2],
- }],
},
proto::Contact {
user_id: 5,
online: false,
should_notify: false,
- projects: vec![],
},
proto::Contact {
user_id: current_user_id,
online: true,
should_notify: false,
- projects: vec![proto::ProjectMetadata {
- id: 103,
- visible_worktree_root_names: vec!["dir3".to_string()],
- guests: vec![3],
- }],
},
],
..Default::default()
});
- assert_eq!(
- server
- .receive::<proto::UpdateProject>()
- .await
- .unwrap()
- .payload,
- proto::UpdateProject {
- project_id: 200,
- online: false,
- worktrees: vec![]
- },
- );
-
- cx.foreground().run_until_parked();
- assert_eq!(
- cx.read(|cx| render_to_strings(&panel, cx)),
- &[
- "v Requests",
- " incoming user_one",
- " outgoing user_two",
- "v Online",
- " the_current_user",
- " dir3",
- " 🔒 private_dir",
- " user_four",
- " dir2",
- " user_three",
- " dir1",
- "v Offline",
- " user_five",
- ]
- );
-
- // Take a project online. It appears as loading, since the project
- // isn't yet visible to other contacts.
- project.update(cx, |project, cx| project.set_online(true, cx));
- cx.foreground().run_until_parked();
- assert_eq!(
- cx.read(|cx| render_to_strings(&panel, cx)),
- &[
- "v Requests",
- " incoming user_one",
- " outgoing user_two",
- "v Online",
- " the_current_user",
- " dir3",
- " 🔒 private_dir (going online...)",
- " user_four",
- " dir2",
- " user_three",
- " dir1",
- "v Offline",
- " user_five",
- ]
- );
-
- // The server receives the project's metadata and updates the contact metadata
- // for the current user. Now the project appears as online.
- assert_eq!(
- server
- .receive::<proto::UpdateProject>()
- .await
- .unwrap()
- .payload,
- proto::UpdateProject {
- project_id: 200,
- online: true,
- worktrees: vec![proto::WorktreeMetadata {
- id: worktree_id,
- root_name: "private_dir".to_string(),
- visible: true,
- }]
- },
- );
- server
- .receive::<proto::UpdateWorktreeExtensions>()
- .await
- .unwrap();
-
- server.send(proto::UpdateContacts {
- contacts: vec![proto::Contact {
- user_id: current_user_id,
- online: true,
- should_notify: false,
- projects: vec![
- proto::ProjectMetadata {
- id: 103,
- visible_worktree_root_names: vec!["dir3".to_string()],
- guests: vec![3],
- },
- proto::ProjectMetadata {
- id: 200,
- visible_worktree_root_names: vec!["private_dir".to_string()],
- guests: vec![3],
- },
- ],
- }],
- ..Default::default()
- });
- cx.foreground().run_until_parked();
- assert_eq!(
- cx.read(|cx| render_to_strings(&panel, cx)),
- &[
- "v Requests",
- " incoming user_one",
- " outgoing user_two",
- "v Online",
- " the_current_user",
- " dir3",
- " private_dir",
- " user_four",
- " dir2",
- " user_three",
- " dir1",
- "v Offline",
- " user_five",
- ]
- );
-
- // Take the project offline. It appears as loading.
- project.update(cx, |project, cx| project.set_online(false, cx));
- cx.foreground().run_until_parked();
- assert_eq!(
- cx.read(|cx| render_to_strings(&panel, cx)),
- &[
- "v Requests",
- " incoming user_one",
- " outgoing user_two",
- "v Online",
- " the_current_user",
- " dir3",
- " private_dir (going offline...)",
- " user_four",
- " dir2",
- " user_three",
- " dir1",
- "v Offline",
- " user_five",
- ]
- );
-
- // The server receives the unregister request and updates the contact
- // metadata for the current user. The project is now offline.
- assert_eq!(
- server
- .receive::<proto::UpdateProject>()
- .await
- .unwrap()
- .payload,
- proto::UpdateProject {
- project_id: 200,
- online: false,
- worktrees: vec![]
- },
- );
-
- server.send(proto::UpdateContacts {
- contacts: vec![proto::Contact {
- user_id: current_user_id,
- online: true,
- should_notify: false,
- projects: vec![proto::ProjectMetadata {
- id: 103,
- visible_worktree_root_names: vec!["dir3".to_string()],
- guests: vec![3],
- }],
- }],
- ..Default::default()
- });
cx.foreground().run_until_parked();
assert_eq!(
cx.read(|cx| render_to_strings(&panel, cx)),
@@ -1529,12 +968,8 @@ mod tests {
" outgoing user_two",
"v Online",
" the_current_user",
- " dir3",
- " 🔒 private_dir",
" user_four",
- " dir2",
" user_three",
- " dir1",
"v Offline",
" user_five",
]
@@ -1551,7 +986,6 @@ mod tests {
&[
"v Online",
" user_four <=== selected",
- " dir2",
"v Offline",
" user_five",
]
@@ -1565,8 +999,7 @@ mod tests {
&[
"v Online",
" user_four",
- " dir2 <=== selected",
- "v Offline",
+ "v Offline <=== selected",
" user_five",
]
);
@@ -1579,9 +1012,8 @@ mod tests {
&[
"v Online",
" user_four",
- " dir2",
- "v Offline <=== selected",
- " user_five",
+ "v Offline",
+ " user_five <=== selected",
]
);
}
@@ -1608,37 +1040,6 @@ mod tests {
ContactEntry::Contact(contact) => {
format!(" {}", contact.user.github_login)
}
- ContactEntry::ContactProject(contact, project_ix, project) => {
- let project = project
- .and_then(|p| p.upgrade(cx))
- .map(|project| project.read(cx));
- format!(
- " {}{}",
- contact.projects[*project_ix]
- .visible_worktree_root_names
- .join(", "),
- if project.map_or(true, |project| project.is_online()) {
- ""
- } else {
- " (going offline...)"
- },
- )
- }
- ContactEntry::OfflineProject(project) => {
- let project = project.upgrade(cx).unwrap().read(cx);
- format!(
- " 🔒 {}{}",
- project
- .worktree_root_names(cx)
- .collect::<Vec<_>>()
- .join(", "),
- if project.is_online() {
- " (going online...)"
- } else {
- ""
- },
- )
- }
};
if panel.selection == Some(ix) {
@@ -1048,15 +1048,8 @@ message ChannelMessage {
message Contact {
uint64 user_id = 1;
- repeated ProjectMetadata projects = 2;
- bool online = 3;
- bool should_notify = 4;
-}
-
-message ProjectMetadata {
- uint64 id = 1;
- repeated string visible_worktree_root_names = 3;
- repeated uint64 guests = 4;
+ bool online = 2;
+ bool should_notify = 3;
}
message WorktreeMetadata {
@@ -1,185 +0,0 @@
-use crate::{sidebar::SidebarSide, AppState, ToggleFollow, Workspace};
-use anyhow::Result;
-use client::{proto, Client, Contact};
-use gpui::{
- elements::*, ElementBox, Entity, ImageData, MutableAppContext, RenderContext, Task, View,
- ViewContext,
-};
-use project::Project;
-use settings::Settings;
-use std::sync::Arc;
-use util::ResultExt;
-
-pub struct WaitingRoom {
- project_id: u64,
- avatar: Option<Arc<ImageData>>,
- message: String,
- waiting: bool,
- client: Arc<Client>,
- _join_task: Task<Result<()>>,
-}
-
-impl Entity for WaitingRoom {
- type Event = ();
-
- fn release(&mut self, _: &mut MutableAppContext) {
- if self.waiting {
- self.client
- .send(proto::LeaveProject {
- project_id: self.project_id,
- })
- .log_err();
- }
- }
-}
-
-impl View for WaitingRoom {
- fn ui_name() -> &'static str {
- "WaitingRoom"
- }
-
- fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- let theme = &cx.global::<Settings>().theme.workspace;
-
- Flex::column()
- .with_children(self.avatar.clone().map(|avatar| {
- Image::new(avatar)
- .with_style(theme.joining_project_avatar)
- .aligned()
- .boxed()
- }))
- .with_child(
- Text::new(
- self.message.clone(),
- theme.joining_project_message.text.clone(),
- )
- .contained()
- .with_style(theme.joining_project_message.container)
- .aligned()
- .boxed(),
- )
- .aligned()
- .contained()
- .with_background_color(theme.background)
- .boxed()
- }
-}
-
-impl WaitingRoom {
- pub fn new(
- contact: Arc<Contact>,
- project_index: usize,
- app_state: Arc<AppState>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
- let project_id = contact.projects[project_index].id;
- let client = app_state.client.clone();
- let _join_task = cx.spawn_weak({
- let contact = contact.clone();
- |this, mut cx| async move {
- let project = Project::remote(
- project_id,
- app_state.client.clone(),
- app_state.user_store.clone(),
- app_state.project_store.clone(),
- app_state.languages.clone(),
- app_state.fs.clone(),
- cx.clone(),
- )
- .await;
-
- if let Some(this) = this.upgrade(&cx) {
- this.update(&mut cx, |this, cx| {
- this.waiting = false;
- match project {
- Ok(project) => {
- cx.replace_root_view(|cx| {
- let mut workspace =
- Workspace::new(project, app_state.default_item_factory, cx);
- (app_state.initialize_workspace)(
- &mut workspace,
- &app_state,
- cx,
- );
- workspace.toggle_sidebar(SidebarSide::Left, cx);
- if let Some((host_peer_id, _)) = workspace
- .project
- .read(cx)
- .collaborators()
- .iter()
- .find(|(_, collaborator)| collaborator.replica_id == 0)
- {
- if let Some(follow) = workspace
- .toggle_follow(&ToggleFollow(*host_peer_id), cx)
- {
- follow.detach_and_log_err(cx);
- }
- }
- workspace
- });
- }
- Err(error) => {
- let login = &contact.user.github_login;
- let message = match error {
- project::JoinProjectError::HostDeclined => {
- format!("@{} declined your request.", login)
- }
- project::JoinProjectError::HostClosedProject => {
- format!(
- "@{} closed their copy of {}.",
- login,
- humanize_list(
- &contact.projects[project_index]
- .visible_worktree_root_names
- )
- )
- }
- project::JoinProjectError::HostWentOffline => {
- format!("@{} went offline.", login)
- }
- project::JoinProjectError::Other(error) => {
- log::error!("error joining project: {}", error);
- "An error occurred.".to_string()
- }
- };
- this.message = message;
- cx.notify();
- }
- }
- })
- }
-
- Ok(())
- }
- });
-
- Self {
- project_id,
- avatar: contact.user.avatar.clone(),
- message: format!(
- "Asking to join @{}'s copy of {}...",
- contact.user.github_login,
- humanize_list(&contact.projects[project_index].visible_worktree_root_names)
- ),
- waiting: true,
- client,
- _join_task,
- }
- }
-}
-
-fn humanize_list<'a>(items: impl IntoIterator<Item = &'a String>) -> String {
- let mut list = String::new();
- let mut items = items.into_iter().enumerate().peekable();
- while let Some((ix, item)) = items.next() {
- if ix > 0 {
- list.push_str(", ");
- if items.peek().is_none() {
- list.push_str("and ");
- }
- }
-
- list.push_str(item);
- }
- list
-}
@@ -10,7 +10,6 @@ pub mod searchable;
pub mod sidebar;
mod status_bar;
mod toolbar;
-mod waiting_room;
use anyhow::{anyhow, Context, Result};
use client::{proto, Client, Contact, PeerId, Subscription, TypedEnvelope, UserStore};
@@ -58,7 +57,6 @@ use std::{
use theme::{Theme, ThemeRegistry};
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
use util::ResultExt;
-use waiting_room::WaitingRoom;
type ProjectItemBuilders = HashMap<
TypeId,
@@ -167,14 +165,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
}
}
});
- cx.add_global_action({
- let app_state = Arc::downgrade(&app_state);
- move |action: &JoinProject, cx: &mut MutableAppContext| {
- if let Some(app_state) = app_state.upgrade() {
- join_project(action.contact.clone(), action.project_index, &app_state, cx);
- }
- }
- });
cx.add_async_action(Workspace::toggle_follow);
cx.add_async_action(Workspace::follow_next_collaborator);
@@ -2663,28 +2653,6 @@ pub fn open_paths(
})
}
-pub fn join_project(
- contact: Arc<Contact>,
- project_index: usize,
- app_state: &Arc<AppState>,
- cx: &mut MutableAppContext,
-) {
- let project_id = contact.projects[project_index].id;
-
- for window_id in cx.window_ids().collect::<Vec<_>>() {
- if let Some(workspace) = cx.root_view::<Workspace>(window_id) {
- if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) {
- cx.activate_window(window_id);
- return;
- }
- }
- }
-
- cx.add_window((app_state.build_window_options)(), |cx| {
- WaitingRoom::new(contact, project_index, app_state.clone(), cx)
- });
-}
-
fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| {
let mut workspace = Workspace::new(