waiting_room.rs

  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                                                        .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].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}