zed/crates/workspace/src/waiting_room.rs

184 lines
6.9 KiB
Rust

use crate::{sidebar::Side, 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, cx);
(app_state.initialize_workspace)(
&mut workspace,
&app_state,
cx,
);
workspace.toggle_sidebar(Side::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
}