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