mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 10:40:54 +00:00
Cancel join requests when the requester closes the window
This commit is contained in:
parent
7c3eebf93e
commit
d821e7a4c1
10 changed files with 449 additions and 214 deletions
|
@ -23,6 +23,8 @@ impl PartialEq for User {
|
|||
}
|
||||
}
|
||||
|
||||
impl Eq for User {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Contact {
|
||||
pub user: Arc<User>,
|
||||
|
|
|
@ -650,9 +650,10 @@ impl Server {
|
|||
let project_id = request.payload.project_id;
|
||||
let project;
|
||||
{
|
||||
let mut state = self.store_mut().await;
|
||||
project = state.leave_project(sender_id, project_id)?;
|
||||
let unshare = project.connection_ids.len() <= 1;
|
||||
let mut store = self.store_mut().await;
|
||||
project = store.leave_project(sender_id, project_id)?;
|
||||
|
||||
if project.remove_collaborator {
|
||||
broadcast(sender_id, project.connection_ids, |conn_id| {
|
||||
self.peer.send(
|
||||
conn_id,
|
||||
|
@ -662,7 +663,19 @@ impl Server {
|
|||
},
|
||||
)
|
||||
});
|
||||
if unshare {
|
||||
}
|
||||
|
||||
if let Some(requester_id) = project.cancel_request {
|
||||
self.peer.send(
|
||||
project.host_connection_id,
|
||||
proto::JoinProjectRequestCancelled {
|
||||
project_id,
|
||||
requester_id: requester_id.to_proto(),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
if project.unshare {
|
||||
self.peer.send(
|
||||
project.host_connection_id,
|
||||
proto::ProjectUnshared { project_id },
|
||||
|
@ -1633,6 +1646,7 @@ mod tests {
|
|||
use settings::Settings;
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
env,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -2049,6 +2063,105 @@ mod tests {
|
|||
));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_cancel_join_request(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
) {
|
||||
let lang_registry = Arc::new(LanguageRegistry::test());
|
||||
let fs = FakeFs::new(cx_a.background());
|
||||
cx_a.foreground().forbid_parking();
|
||||
|
||||
// Connect to a server as 2 clients.
|
||||
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;
|
||||
server
|
||||
.make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
|
||||
.await;
|
||||
|
||||
// Share a project as client A
|
||||
fs.insert_tree("/a", json!({})).await;
|
||||
let project_a = cx_a.update(|cx| {
|
||||
Project::local(
|
||||
client_a.clone(),
|
||||
client_a.user_store.clone(),
|
||||
lang_registry.clone(),
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let project_id = project_a
|
||||
.read_with(cx_a, |project, _| project.next_remote_id())
|
||||
.await;
|
||||
|
||||
let project_a_events = Rc::new(RefCell::new(Vec::new()));
|
||||
let user_b = client_a
|
||||
.user_store
|
||||
.update(cx_a, |store, cx| {
|
||||
store.fetch_user(client_b.user_id().unwrap(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
project_a.update(cx_a, {
|
||||
let project_a_events = project_a_events.clone();
|
||||
move |_, cx| {
|
||||
cx.subscribe(&cx.handle(), move |_, _, event, _| {
|
||||
project_a_events.borrow_mut().push(event.clone());
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
let (worktree_a, _) = project_a
|
||||
.update(cx_a, |p, cx| {
|
||||
p.find_or_create_local_worktree("/a", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
worktree_a
|
||||
.read_with(cx_a, |tree, _| tree.as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
// Request to join that project as client B
|
||||
let project_b = cx_b.spawn(|mut cx| {
|
||||
let client = client_b.client.clone();
|
||||
let user_store = client_b.user_store.clone();
|
||||
let lang_registry = lang_registry.clone();
|
||||
async move {
|
||||
Project::remote(
|
||||
project_id,
|
||||
client,
|
||||
user_store,
|
||||
lang_registry.clone(),
|
||||
FakeFs::new(cx.background()),
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
assert_eq!(
|
||||
&*project_a_events.borrow(),
|
||||
&[project::Event::ContactRequestedJoin(user_b.clone())]
|
||||
);
|
||||
project_a_events.borrow_mut().clear();
|
||||
|
||||
// Cancel the join request by leaving the project
|
||||
client_b
|
||||
.client
|
||||
.send(proto::LeaveProject { project_id })
|
||||
.unwrap();
|
||||
drop(project_b);
|
||||
|
||||
deterministic.run_until_parked();
|
||||
assert_eq!(
|
||||
&*project_a_events.borrow(),
|
||||
&[project::Event::ContactCancelledJoinRequest(user_b.clone())]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_propagate_saves_and_fs_changes(
|
||||
cx_a: &mut TestAppContext,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::db::{self, ChannelId, UserId};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
|
||||
use rpc::{proto, ConnectionId, Receipt};
|
||||
use std::{collections::hash_map, path::PathBuf};
|
||||
use tracing::instrument;
|
||||
|
@ -56,9 +56,12 @@ pub struct RemovedConnectionState {
|
|||
}
|
||||
|
||||
pub struct LeftProject {
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
pub host_user_id: UserId,
|
||||
pub host_connection_id: ConnectionId,
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
pub remove_collaborator: bool,
|
||||
pub cancel_request: Option<UserId>,
|
||||
pub unshare: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -503,24 +506,48 @@ impl Store {
|
|||
connection_id: ConnectionId,
|
||||
project_id: u64,
|
||||
) -> Result<LeftProject> {
|
||||
let user_id = self.user_id_for_connection(connection_id)?;
|
||||
let project = self
|
||||
.projects
|
||||
.get_mut(&project_id)
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
let (replica_id, _) = project
|
||||
.guests
|
||||
.remove(&connection_id)
|
||||
.ok_or_else(|| anyhow!("cannot leave a project before joining it"))?;
|
||||
|
||||
// If the connection leaving the project is a collaborator, remove it.
|
||||
let remove_collaborator =
|
||||
if let Some((replica_id, _)) = project.guests.remove(&connection_id) {
|
||||
project.active_replica_ids.remove(&replica_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// If the connection leaving the project has a pending request, remove it.
|
||||
// If that user has no other pending requests on other connections, indicate that the request should be cancelled.
|
||||
let mut cancel_request = None;
|
||||
if let Entry::Occupied(mut entry) = project.join_requests.entry(user_id) {
|
||||
entry
|
||||
.get_mut()
|
||||
.retain(|receipt| receipt.sender_id != connection_id);
|
||||
if entry.get().is_empty() {
|
||||
entry.remove();
|
||||
cancel_request = Some(user_id);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(connection) = self.connections.get_mut(&connection_id) {
|
||||
connection.projects.remove(&project_id);
|
||||
}
|
||||
|
||||
let connection_ids = project.connection_ids();
|
||||
let unshare = connection_ids.len() <= 1 && project.join_requests.is_empty();
|
||||
|
||||
Ok(LeftProject {
|
||||
connection_ids: project.connection_ids(),
|
||||
host_connection_id: project.host_connection_id,
|
||||
host_user_id: project.host_user_id,
|
||||
connection_ids,
|
||||
cancel_request,
|
||||
unshare,
|
||||
remove_collaborator,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3231,6 +3231,21 @@ impl<'a, T: View> ViewContext<'a, T> {
|
|||
self.app.add_option_view(self.window_id, build_view)
|
||||
}
|
||||
|
||||
pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
|
||||
where
|
||||
V: View,
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
{
|
||||
let window_id = self.window_id;
|
||||
self.update(|this| {
|
||||
let root_view = this.add_view(window_id, build_root_view);
|
||||
let window = this.cx.windows.get_mut(&window_id).unwrap();
|
||||
window.root_view = root_view.clone().into();
|
||||
window.focused_view_id = Some(root_view.id());
|
||||
root_view
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
|
||||
where
|
||||
E: Entity,
|
||||
|
|
|
@ -136,7 +136,7 @@ pub struct Collaborator {
|
|||
pub replica_id: ReplicaId,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
ActiveEntryChanged(Option<ProjectEntryId>),
|
||||
WorktreeRemoved(WorktreeId),
|
||||
|
@ -147,6 +147,7 @@ pub enum Event {
|
|||
RemoteIdChanged(Option<u64>),
|
||||
CollaboratorLeft(PeerId),
|
||||
ContactRequestedJoin(Arc<User>),
|
||||
ContactCancelledJoinRequest(Arc<User>),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -269,6 +270,7 @@ impl Project {
|
|||
client.add_model_message_handler(Self::handle_start_language_server);
|
||||
client.add_model_message_handler(Self::handle_update_language_server);
|
||||
client.add_model_message_handler(Self::handle_remove_collaborator);
|
||||
client.add_model_message_handler(Self::handle_join_project_request_cancelled);
|
||||
client.add_model_message_handler(Self::handle_register_worktree);
|
||||
client.add_model_message_handler(Self::handle_unregister_worktree);
|
||||
client.add_model_message_handler(Self::handle_unregister_project);
|
||||
|
@ -3879,6 +3881,27 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_join_project_request_cancelled(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::JoinProjectRequestCancelled>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let user = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.user_store.update(cx, |user_store, cx| {
|
||||
user_store.fetch_user(envelope.payload.requester_id, cx)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |_, cx| {
|
||||
cx.emit(Event::ContactCancelledJoinRequest(user));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_register_worktree(
|
||||
this: ModelHandle<Self>,
|
||||
envelope: TypedEnvelope<proto::RegisterWorktree>,
|
||||
|
|
|
@ -16,88 +16,89 @@ message Envelope {
|
|||
UnregisterProject unregister_project = 10;
|
||||
RequestJoinProject request_join_project = 11;
|
||||
RespondToJoinProjectRequest respond_to_join_project_request = 12;
|
||||
JoinProject join_project = 13;
|
||||
JoinProjectResponse join_project_response = 14;
|
||||
LeaveProject leave_project = 15;
|
||||
AddProjectCollaborator add_project_collaborator = 16;
|
||||
RemoveProjectCollaborator remove_project_collaborator = 17;
|
||||
ProjectUnshared project_unshared = 18;
|
||||
JoinProjectRequestCancelled join_project_request_cancelled = 13;
|
||||
JoinProject join_project = 14;
|
||||
JoinProjectResponse join_project_response = 15;
|
||||
LeaveProject leave_project = 16;
|
||||
AddProjectCollaborator add_project_collaborator = 17;
|
||||
RemoveProjectCollaborator remove_project_collaborator = 18;
|
||||
ProjectUnshared project_unshared = 19;
|
||||
|
||||
GetDefinition get_definition = 19;
|
||||
GetDefinitionResponse get_definition_response = 20;
|
||||
GetReferences get_references = 21;
|
||||
GetReferencesResponse get_references_response = 22;
|
||||
GetDocumentHighlights get_document_highlights = 23;
|
||||
GetDocumentHighlightsResponse get_document_highlights_response = 24;
|
||||
GetProjectSymbols get_project_symbols = 25;
|
||||
GetProjectSymbolsResponse get_project_symbols_response = 26;
|
||||
OpenBufferForSymbol open_buffer_for_symbol = 27;
|
||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 28;
|
||||
GetDefinition get_definition = 20;
|
||||
GetDefinitionResponse get_definition_response = 21;
|
||||
GetReferences get_references = 22;
|
||||
GetReferencesResponse get_references_response = 23;
|
||||
GetDocumentHighlights get_document_highlights = 24;
|
||||
GetDocumentHighlightsResponse get_document_highlights_response = 25;
|
||||
GetProjectSymbols get_project_symbols = 26;
|
||||
GetProjectSymbolsResponse get_project_symbols_response = 27;
|
||||
OpenBufferForSymbol open_buffer_for_symbol = 28;
|
||||
OpenBufferForSymbolResponse open_buffer_for_symbol_response = 29;
|
||||
|
||||
RegisterWorktree register_worktree = 29;
|
||||
UnregisterWorktree unregister_worktree = 30;
|
||||
UpdateWorktree update_worktree = 31;
|
||||
RegisterWorktree register_worktree = 30;
|
||||
UnregisterWorktree unregister_worktree = 31;
|
||||
UpdateWorktree update_worktree = 32;
|
||||
|
||||
CreateProjectEntry create_project_entry = 32;
|
||||
RenameProjectEntry rename_project_entry = 33;
|
||||
DeleteProjectEntry delete_project_entry = 34;
|
||||
ProjectEntryResponse project_entry_response = 35;
|
||||
CreateProjectEntry create_project_entry = 33;
|
||||
RenameProjectEntry rename_project_entry = 34;
|
||||
DeleteProjectEntry delete_project_entry = 35;
|
||||
ProjectEntryResponse project_entry_response = 36;
|
||||
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 36;
|
||||
StartLanguageServer start_language_server = 37;
|
||||
UpdateLanguageServer update_language_server = 38;
|
||||
UpdateDiagnosticSummary update_diagnostic_summary = 37;
|
||||
StartLanguageServer start_language_server = 38;
|
||||
UpdateLanguageServer update_language_server = 39;
|
||||
|
||||
OpenBufferById open_buffer_by_id = 39;
|
||||
OpenBufferByPath open_buffer_by_path = 40;
|
||||
OpenBufferResponse open_buffer_response = 41;
|
||||
UpdateBuffer update_buffer = 42;
|
||||
UpdateBufferFile update_buffer_file = 43;
|
||||
SaveBuffer save_buffer = 44;
|
||||
BufferSaved buffer_saved = 45;
|
||||
BufferReloaded buffer_reloaded = 46;
|
||||
ReloadBuffers reload_buffers = 47;
|
||||
ReloadBuffersResponse reload_buffers_response = 48;
|
||||
FormatBuffers format_buffers = 49;
|
||||
FormatBuffersResponse format_buffers_response = 50;
|
||||
GetCompletions get_completions = 51;
|
||||
GetCompletionsResponse get_completions_response = 52;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 53;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 54;
|
||||
GetCodeActions get_code_actions = 55;
|
||||
GetCodeActionsResponse get_code_actions_response = 56;
|
||||
ApplyCodeAction apply_code_action = 57;
|
||||
ApplyCodeActionResponse apply_code_action_response = 58;
|
||||
PrepareRename prepare_rename = 59;
|
||||
PrepareRenameResponse prepare_rename_response = 60;
|
||||
PerformRename perform_rename = 61;
|
||||
PerformRenameResponse perform_rename_response = 62;
|
||||
SearchProject search_project = 63;
|
||||
SearchProjectResponse search_project_response = 64;
|
||||
OpenBufferById open_buffer_by_id = 40;
|
||||
OpenBufferByPath open_buffer_by_path = 41;
|
||||
OpenBufferResponse open_buffer_response = 42;
|
||||
UpdateBuffer update_buffer = 43;
|
||||
UpdateBufferFile update_buffer_file = 44;
|
||||
SaveBuffer save_buffer = 45;
|
||||
BufferSaved buffer_saved = 46;
|
||||
BufferReloaded buffer_reloaded = 47;
|
||||
ReloadBuffers reload_buffers = 48;
|
||||
ReloadBuffersResponse reload_buffers_response = 49;
|
||||
FormatBuffers format_buffers = 50;
|
||||
FormatBuffersResponse format_buffers_response = 51;
|
||||
GetCompletions get_completions = 52;
|
||||
GetCompletionsResponse get_completions_response = 53;
|
||||
ApplyCompletionAdditionalEdits apply_completion_additional_edits = 54;
|
||||
ApplyCompletionAdditionalEditsResponse apply_completion_additional_edits_response = 55;
|
||||
GetCodeActions get_code_actions = 56;
|
||||
GetCodeActionsResponse get_code_actions_response = 57;
|
||||
ApplyCodeAction apply_code_action = 58;
|
||||
ApplyCodeActionResponse apply_code_action_response = 59;
|
||||
PrepareRename prepare_rename = 60;
|
||||
PrepareRenameResponse prepare_rename_response = 61;
|
||||
PerformRename perform_rename = 62;
|
||||
PerformRenameResponse perform_rename_response = 63;
|
||||
SearchProject search_project = 64;
|
||||
SearchProjectResponse search_project_response = 65;
|
||||
|
||||
GetChannels get_channels = 65;
|
||||
GetChannelsResponse get_channels_response = 66;
|
||||
JoinChannel join_channel = 67;
|
||||
JoinChannelResponse join_channel_response = 68;
|
||||
LeaveChannel leave_channel = 69;
|
||||
SendChannelMessage send_channel_message = 70;
|
||||
SendChannelMessageResponse send_channel_message_response = 71;
|
||||
ChannelMessageSent channel_message_sent = 72;
|
||||
GetChannelMessages get_channel_messages = 73;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 74;
|
||||
GetChannels get_channels = 66;
|
||||
GetChannelsResponse get_channels_response = 67;
|
||||
JoinChannel join_channel = 68;
|
||||
JoinChannelResponse join_channel_response = 69;
|
||||
LeaveChannel leave_channel = 70;
|
||||
SendChannelMessage send_channel_message = 71;
|
||||
SendChannelMessageResponse send_channel_message_response = 72;
|
||||
ChannelMessageSent channel_message_sent = 73;
|
||||
GetChannelMessages get_channel_messages = 74;
|
||||
GetChannelMessagesResponse get_channel_messages_response = 75;
|
||||
|
||||
UpdateContacts update_contacts = 75;
|
||||
UpdateContacts update_contacts = 76;
|
||||
|
||||
GetUsers get_users = 76;
|
||||
FuzzySearchUsers fuzzy_search_users = 77;
|
||||
UsersResponse users_response = 78;
|
||||
RequestContact request_contact = 79;
|
||||
RespondToContactRequest respond_to_contact_request = 80;
|
||||
RemoveContact remove_contact = 81;
|
||||
GetUsers get_users = 77;
|
||||
FuzzySearchUsers fuzzy_search_users = 78;
|
||||
UsersResponse users_response = 79;
|
||||
RequestContact request_contact = 80;
|
||||
RespondToContactRequest respond_to_contact_request = 81;
|
||||
RemoveContact remove_contact = 82;
|
||||
|
||||
Follow follow = 82;
|
||||
FollowResponse follow_response = 83;
|
||||
UpdateFollowers update_followers = 84;
|
||||
Unfollow unfollow = 85;
|
||||
Follow follow = 83;
|
||||
FollowResponse follow_response = 84;
|
||||
UpdateFollowers update_followers = 85;
|
||||
Unfollow unfollow = 86;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +137,11 @@ message RespondToJoinProjectRequest {
|
|||
bool allow = 3;
|
||||
}
|
||||
|
||||
message JoinProjectRequestCancelled {
|
||||
uint64 requester_id = 1;
|
||||
uint64 project_id = 2;
|
||||
}
|
||||
|
||||
message JoinProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ messages!(
|
|||
(JoinChannelResponse, Foreground),
|
||||
(JoinProject, Foreground),
|
||||
(JoinProjectResponse, Foreground),
|
||||
(JoinProjectRequestCancelled, Foreground),
|
||||
(LeaveChannel, Foreground),
|
||||
(LeaveProject, Foreground),
|
||||
(OpenBufferById, Background),
|
||||
|
@ -220,6 +221,7 @@ entity_messages!(
|
|||
GetReferences,
|
||||
GetProjectSymbols,
|
||||
JoinProject,
|
||||
JoinProjectRequestCancelled,
|
||||
LeaveProject,
|
||||
OpenBufferById,
|
||||
OpenBufferByPath,
|
||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
|||
pub use peer::*;
|
||||
mod macros;
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 17;
|
||||
pub const PROTOCOL_VERSION: u32 = 18;
|
||||
|
|
159
crates/workspace/src/waiting_room.rs
Normal file
159
crates/workspace/src/waiting_room.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use crate::{
|
||||
sidebar::{Side, ToggleSidebarItem},
|
||||
AppState,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use client::Contact;
|
||||
use gpui::{elements::*, ElementBox, Entity, ImageData, RenderContext, Task, View, ViewContext};
|
||||
use project::Project;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct WaitingRoom {
|
||||
avatar: Option<Arc<ImageData>>,
|
||||
message: String,
|
||||
joined: bool,
|
||||
_join_task: Task<Result<()>>,
|
||||
}
|
||||
|
||||
impl Entity for WaitingRoom {
|
||||
type Event = ();
|
||||
|
||||
fn release(&mut self, _: &mut gpui::MutableAppContext) {
|
||||
if !self.joined {
|
||||
// TODO: Cancel the join request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 _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.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| match project {
|
||||
Ok(project) => {
|
||||
this.joined = true;
|
||||
cx.replace_root_view(|cx| {
|
||||
let mut workspace =
|
||||
(app_state.build_workspace)(project, &app_state, cx);
|
||||
workspace.toggle_sidebar_item(
|
||||
&ToggleSidebarItem {
|
||||
side: Side::Left,
|
||||
item_index: 0,
|
||||
},
|
||||
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].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 {
|
||||
avatar: contact.user.avatar.clone(),
|
||||
message: format!(
|
||||
"Asking to join @{}'s copy of {}...",
|
||||
contact.user.github_login,
|
||||
humanize_list(&contact.projects[project_index].worktree_root_names)
|
||||
),
|
||||
joined: false,
|
||||
_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
|
||||
}
|
|
@ -5,6 +5,7 @@ pub mod pane_group;
|
|||
pub mod sidebar;
|
||||
mod status_bar;
|
||||
mod toolbar;
|
||||
mod waiting_room;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use client::{
|
||||
|
@ -50,6 +51,7 @@ use std::{
|
|||
use theme::{Theme, ThemeRegistry};
|
||||
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
||||
use util::ResultExt;
|
||||
use waiting_room::WaitingRoom;
|
||||
|
||||
type ProjectItemBuilders = HashMap<
|
||||
TypeId,
|
||||
|
@ -124,8 +126,7 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
|
|||
action.project_index,
|
||||
&action.app_state,
|
||||
cx,
|
||||
)
|
||||
.detach();
|
||||
);
|
||||
});
|
||||
|
||||
cx.add_async_action(Workspace::toggle_follow);
|
||||
|
@ -2280,119 +2281,21 @@ pub fn join_project(
|
|||
project_index: usize,
|
||||
app_state: &Arc<AppState>,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> Task<Result<ViewHandle<Workspace>>> {
|
||||
) {
|
||||
let project_id = contact.projects[project_index].id;
|
||||
|
||||
struct JoiningNotice {
|
||||
avatar: Option<Arc<ImageData>>,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl Entity for JoiningNotice {
|
||||
type Event = ();
|
||||
}
|
||||
|
||||
impl View for JoiningNotice {
|
||||
fn ui_name() -> &'static str {
|
||||
"JoiningProjectWindow"
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return Task::ready(Ok(workspace));
|
||||
cx.activate_window(window_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let app_state = app_state.clone();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let (window, joining_notice) = cx.update(|cx| {
|
||||
cx.add_window((app_state.build_window_options)(), |_| JoiningNotice {
|
||||
avatar: contact.user.avatar.clone(),
|
||||
message: format!(
|
||||
"Asking to join @{}'s copy of {}...",
|
||||
contact.user.github_login,
|
||||
humanize_list(&contact.projects[project_index].worktree_root_names)
|
||||
),
|
||||
})
|
||||
cx.add_window((app_state.build_window_options)(), |cx| {
|
||||
WaitingRoom::new(contact, project_index, app_state.clone(), cx)
|
||||
});
|
||||
let project = Project::remote(
|
||||
project_id,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
cx.update(|cx| match project {
|
||||
Ok(project) => Ok(cx.replace_root_view(window, |cx| {
|
||||
let mut workspace = (app_state.build_workspace)(project, &app_state, cx);
|
||||
workspace.toggle_sidebar_item(
|
||||
&ToggleSidebarItem {
|
||||
side: Side::Left,
|
||||
item_index: 0,
|
||||
},
|
||||
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].worktree_root_names)
|
||||
)
|
||||
}
|
||||
project::JoinProjectError::HostWentOffline => {
|
||||
format!("@{} went offline.", login)
|
||||
}
|
||||
project::JoinProjectError::Other(_) => "An error occurred.".to_string(),
|
||||
};
|
||||
joining_notice.update(cx, |notice, cx| {
|
||||
notice.message = message;
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
Err(error)?
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
||||
|
@ -2408,18 +2311,3 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
|
|||
});
|
||||
cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue