| @@ -8,7 +8,8 @@ mod toolbar; · 7 unmodified lines |
| 8 | 8 | |
| 9 | 9 | use anyhow::{anyhow, Context, Result}; |
| 10 | 10 | use client::{ |
| 11 | | − proto, Authenticate, ChannelList, Client, PeerId, Subscription, TypedEnvelope, User, UserStore, |
| 11 | + proto, Authenticate, ChannelList, Client, Contact, PeerId, Subscription, TypedEnvelope, User, |
| 12 | + UserStore, |
| 12 | 13 | }; |
| 13 | 14 | use clock::ReplicaId; |
| 14 | 15 | use collections::{hash_map, HashMap, HashSet}; |
| @@ -97,7 +98,8 @@ pub struct ToggleFollow(pub PeerId); · 82 unmodified lines |
| 97 | 98 | |
| 98 | 99 | #[derive(Clone)] |
| 99 | 100 | pub struct JoinProject { |
| 100 | | − pub project_id: u64, |
| 101 | + pub contact: Arc<Contact>, |
| 102 | + pub project_index: usize, |
| 101 | 103 | pub app_state: Arc<AppState>, |
| 102 | 104 | } |
| 103 | 105 | |
| @@ -117,7 +119,13 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) { · 13 unmodified lines |
| 117 | 119 | open_new(&action.0, cx) |
| 118 | 120 | }); |
| 119 | 121 | cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| { |
| 120 | | − join_project(action.project_id, &action.app_state, cx).detach(); |
| 122 | + join_project( |
| 123 | + action.contact.clone(), |
| 124 | + action.project_index, |
| 125 | + &action.app_state, |
| 126 | + cx, |
| 127 | + ) |
| 128 | + .detach(); |
| 121 | 129 | }); |
| 122 | 130 | |
| 123 | 131 | cx.add_async_action(Workspace::toggle_follow); |
| @@ -2268,12 +2276,16 @@ pub fn open_paths( · 2144 unmodified lines |
| 2268 | 2276 | } |
| 2269 | 2277 | |
| 2270 | 2278 | pub fn join_project( |
| 2271 | | − project_id: u64, |
| 2279 | + contact: Arc<Contact>, |
| 2280 | + project_index: usize, |
| 2272 | 2281 | app_state: &Arc<AppState>, |
| 2273 | 2282 | cx: &mut MutableAppContext, |
| 2274 | 2283 | ) -> Task<Result<ViewHandle<Workspace>>> { |
| 2284 | + let project_id = contact.projects[project_index].id; |
| 2285 | + |
| 2275 | 2286 | struct JoiningNotice { |
| 2276 | | − message: &'static str, |
| 2287 | + avatar: Option<Arc<ImageData>>, |
| 2288 | + message: String, |
| 2277 | 2289 | } |
| 2278 | 2290 | |
| 2279 | 2291 | impl Entity for JoiningNotice { |
| @@ -2287,16 +2299,28 @@ pub fn join_project( · 7 unmodified lines |
| 2287 | 2299 | |
| 2288 | 2300 | fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox { |
| 2289 | 2301 | let theme = &cx.global::<Settings>().theme.workspace; |
| 2290 | | − Text::new( |
| 2291 | | − self.message.to_string(), |
| 2292 | | − theme.joining_project_message.text.clone(), |
| 2293 | | − ) |
| 2294 | | − .contained() |
| 2295 | | − .with_style(theme.joining_project_message.container) |
| 2296 | | − .aligned() |
| 2297 | | − .contained() |
| 2298 | | − .with_background_color(theme.background) |
| 2299 | | − .boxed() |
| 2302 | + |
| 2303 | + Flex::column() |
| 2304 | + .with_children(self.avatar.clone().map(|avatar| { |
| 2305 | + Image::new(avatar) |
| 2306 | + .with_style(theme.joining_project_avatar) |
| 2307 | + .aligned() |
| 2308 | + .boxed() |
| 2309 | + })) |
| 2310 | + .with_child( |
| 2311 | + Text::new( |
| 2312 | + self.message.clone(), |
| 2313 | + theme.joining_project_message.text.clone(), |
| 2314 | + ) |
| 2315 | + .contained() |
| 2316 | + .with_style(theme.joining_project_message.container) |
| 2317 | + .aligned() |
| 2318 | + .boxed(), |
| 2319 | + ) |
| 2320 | + .aligned() |
| 2321 | + .contained() |
| 2322 | + .with_background_color(theme.background) |
| 2323 | + .boxed() |
| 2300 | 2324 | } |
| 2301 | 2325 | } |
| 2302 | 2326 | |
| @@ -2312,7 +2336,12 @@ pub fn join_project( · 9 unmodified lines |
| 2312 | 2336 | cx.spawn(|mut cx| async move { |
| 2313 | 2337 | let (window, joining_notice) = cx.update(|cx| { |
| 2314 | 2338 | cx.add_window((app_state.build_window_options)(), |_| JoiningNotice { |
| 2315 | | − message: "Loading remote project...", |
| 2339 | + avatar: contact.user.avatar.clone(), |
| 2340 | + message: format!( |
| 2341 | + "Asking to join @{}'s copy of {}...", |
| 2342 | + contact.user.github_login, |
| 2343 | + humanize_list(&contact.projects[project_index].worktree_root_names) |
| 2344 | + ), |
| 2316 | 2345 | }) |
| 2317 | 2346 | }); |
| 2318 | 2347 | let project = Project::remote( |
| @@ -2338,15 +2367,22 @@ pub fn join_project( · 19 unmodified lines |
| 2338 | 2367 | workspace |
| 2339 | 2368 | })), |
| 2340 | 2369 | Err(error @ _) => { |
| 2370 | + let login = &contact.user.github_login; |
| 2341 | 2371 | let message = match error { |
| 2342 | 2372 | project::JoinProjectError::HostDeclined => { |
| 2343 | | − "The host declined your request to join." |
| 2373 | + format!("@{} declined your request.", login) |
| 2374 | + } |
| 2375 | + project::JoinProjectError::HostClosedProject => { |
| 2376 | + format!( |
| 2377 | + "@{} closed their copy of {}.", |
| 2378 | + login, |
| 2379 | + humanize_list(&contact.projects[project_index].worktree_root_names) |
| 2380 | + ) |
| 2344 | 2381 | } |
| 2345 | | − project::JoinProjectError::HostClosedProject => "The host closed the project.", |
| 2346 | | − project::JoinProjectError::HostWentOffline => "The host went offline.", |
| 2347 | | − project::JoinProjectError::Other(_) => { |
| 2348 | | − "An error occurred when attempting to join the project." |
| 2382 | + project::JoinProjectError::HostWentOffline => { |
| 2383 | + format!("@{} went offline.", login) |
| 2349 | 2384 | } |
| 2385 | + project::JoinProjectError::Other(_) => "An error occurred.".to_string(), |
| 2350 | 2386 | }; |
| 2351 | 2387 | joining_notice.update(cx, |notice, cx| { |
| 2352 | 2388 | notice.message = message; |
| @@ -2372,3 +2408,18 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) { · 19 unmodified lines |
| 2372 | 2408 | }); |
| 2373 | 2409 | cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); |
| 2374 | 2410 | } |
| 2411 | + |
| 2412 | +fn humanize_list<'a>(items: impl IntoIterator<Item = &'a String>) -> String { |
| 2413 | + let mut list = String::new(); |
| 2414 | + let mut items = items.into_iter().enumerate().peekable(); |
| 2415 | + while let Some((ix, item)) = items.next() { |
| 2416 | + if ix > 0 { |
| 2417 | + list.push_str(", "); |
| 2418 | + } |
| 2419 | + if items.peek().is_none() { |
| 2420 | + list.push_str("and "); |
| 2421 | + } |
| 2422 | + list.push_str(item); |
| 2423 | + } |
| 2424 | + list |
| 2425 | +} |