1use crate::{sidebar::Side, AppState, ToggleFollow, Workspace};
2use anyhow::Result;
3use client::{proto, Client, Contact};
4use gpui::{
5 elements::*, ElementBox, Entity, ImageData, MutableAppContext, RenderContext, Task, View,
6 ViewContext,
7};
8use project::Project;
9use settings::Settings;
10use std::sync::Arc;
11use util::ResultExt;
12
13pub struct WaitingRoom {
14 project_id: u64,
15 avatar: Option<Arc<ImageData>>,
16 message: String,
17 waiting: bool,
18 client: Arc<Client>,
19 _join_task: Task<Result<()>>,
20}
21
22impl Entity for WaitingRoom {
23 type Event = ();
24
25 fn release(&mut self, _: &mut MutableAppContext) {
26 if self.waiting {
27 self.client
28 .send(proto::LeaveProject {
29 project_id: self.project_id,
30 })
31 .log_err();
32 }
33 }
34}
35
36impl View for WaitingRoom {
37 fn ui_name() -> &'static str {
38 "WaitingRoom"
39 }
40
41 fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
42 let theme = &cx.global::<Settings>().theme.workspace;
43
44 Flex::column()
45 .with_children(self.avatar.clone().map(|avatar| {
46 Image::new(avatar)
47 .with_style(theme.joining_project_avatar)
48 .aligned()
49 .boxed()
50 }))
51 .with_child(
52 Text::new(
53 self.message.clone(),
54 theme.joining_project_message.text.clone(),
55 )
56 .contained()
57 .with_style(theme.joining_project_message.container)
58 .aligned()
59 .boxed(),
60 )
61 .aligned()
62 .contained()
63 .with_background_color(theme.background)
64 .boxed()
65 }
66}
67
68impl WaitingRoom {
69 pub fn new(
70 contact: Arc<Contact>,
71 project_index: usize,
72 app_state: Arc<AppState>,
73 cx: &mut ViewContext<Self>,
74 ) -> Self {
75 let project_id = contact.projects[project_index].id;
76 let client = app_state.client.clone();
77 let _join_task =
78 cx.spawn_weak({
79 let contact = contact.clone();
80 |this, mut cx| async move {
81 let project = Project::remote(
82 project_id,
83 app_state.client.clone(),
84 app_state.user_store.clone(),
85 app_state.project_store.clone(),
86 app_state.languages.clone(),
87 app_state.fs.clone(),
88 cx.clone(),
89 )
90 .await;
91
92 if let Some(this) = this.upgrade(&cx) {
93 this.update(&mut cx, |this, cx| {
94 this.waiting = false;
95 match project {
96 Ok(project) => {
97 cx.replace_root_view(|cx| {
98 let mut workspace = Workspace::new(project, cx);
99 (app_state.initialize_workspace)(
100 &mut workspace,
101 &app_state,
102 cx,
103 );
104 workspace.toggle_sidebar(Side::Left, cx);
105 if let Some((host_peer_id, _)) =
106 workspace.project.read(cx).collaborators().iter().find(
107 |(_, collaborator)| collaborator.replica_id == 0,
108 )
109 {
110 if let Some(follow) = workspace
111 .toggle_follow(&ToggleFollow(*host_peer_id), cx)
112 {
113 follow.detach_and_log_err(cx);
114 }
115 }
116 workspace
117 });
118 }
119 Err(error) => {
120 let login = &contact.user.github_login;
121 let message = match error {
122 project::JoinProjectError::HostDeclined => {
123 format!("@{} declined your request.", login)
124 }
125 project::JoinProjectError::HostClosedProject => {
126 format!(
127 "@{} closed their copy of {}.",
128 login,
129 humanize_list(
130 &contact.projects[project_index]
131 .visible_worktree_root_names
132 )
133 )
134 }
135 project::JoinProjectError::HostWentOffline => {
136 format!("@{} went offline.", login)
137 }
138 project::JoinProjectError::Other(error) => {
139 log::error!("error joining project: {}", error);
140 "An error occurred.".to_string()
141 }
142 };
143 this.message = message;
144 cx.notify();
145 }
146 }
147 })
148 }
149
150 Ok(())
151 }
152 });
153
154 Self {
155 project_id,
156 avatar: contact.user.avatar.clone(),
157 message: format!(
158 "Asking to join @{}'s copy of {}...",
159 contact.user.github_login,
160 humanize_list(&contact.projects[project_index].visible_worktree_root_names)
161 ),
162 waiting: true,
163 client,
164 _join_task,
165 }
166 }
167}
168
169fn humanize_list<'a>(items: impl IntoIterator<Item = &'a String>) -> String {
170 let mut list = String::new();
171 let mut items = items.into_iter().enumerate().peekable();
172 while let Some((ix, item)) = items.next() {
173 if ix > 0 {
174 list.push_str(", ");
175 if items.peek().is_none() {
176 list.push_str("and ");
177 }
178 }
179
180 list.push_str(item);
181 }
182 list
183}