waiting_room.rs

  1use crate::{sidebar::SidebarSide, 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 = cx.spawn_weak({
 78            let contact = contact.clone();
 79            |this, mut cx| async move {
 80                let project = Project::remote(
 81                    project_id,
 82                    app_state.client.clone(),
 83                    app_state.user_store.clone(),
 84                    app_state.project_store.clone(),
 85                    app_state.languages.clone(),
 86                    app_state.fs.clone(),
 87                    cx.clone(),
 88                )
 89                .await;
 90
 91                if let Some(this) = this.upgrade(&cx) {
 92                    this.update(&mut cx, |this, cx| {
 93                        this.waiting = false;
 94                        match project {
 95                            Ok(project) => {
 96                                cx.replace_root_view(|cx| {
 97                                    let mut workspace =
 98                                        Workspace::new(project, app_state.default_item_factory, cx);
 99                                    (app_state.initialize_workspace)(
100                                        &mut workspace,
101                                        &app_state,
102                                        cx,
103                                    );
104                                    workspace.toggle_sidebar(SidebarSide::Left, cx);
105                                    if let Some((host_peer_id, _)) = workspace
106                                        .project
107                                        .read(cx)
108                                        .collaborators()
109                                        .iter()
110                                        .find(|(_, collaborator)| collaborator.replica_id == 0)
111                                    {
112                                        if let Some(follow) = workspace
113                                            .toggle_follow(&ToggleFollow(*host_peer_id), cx)
114                                        {
115                                            follow.detach_and_log_err(cx);
116                                        }
117                                    }
118                                    workspace
119                                });
120                            }
121                            Err(error) => {
122                                let login = &contact.user.github_login;
123                                let message = match error {
124                                    project::JoinProjectError::HostDeclined => {
125                                        format!("@{} declined your request.", login)
126                                    }
127                                    project::JoinProjectError::HostClosedProject => {
128                                        format!(
129                                            "@{} closed their copy of {}.",
130                                            login,
131                                            humanize_list(
132                                                &contact.projects[project_index]
133                                                    .visible_worktree_root_names
134                                            )
135                                        )
136                                    }
137                                    project::JoinProjectError::HostWentOffline => {
138                                        format!("@{} went offline.", login)
139                                    }
140                                    project::JoinProjectError::Other(error) => {
141                                        log::error!("error joining project: {}", error);
142                                        "An error occurred.".to_string()
143                                    }
144                                };
145                                this.message = message;
146                                cx.notify();
147                            }
148                        }
149                    })
150                }
151
152                Ok(())
153            }
154        });
155
156        Self {
157            project_id,
158            avatar: contact.user.avatar.clone(),
159            message: format!(
160                "Asking to join @{}'s copy of {}...",
161                contact.user.github_login,
162                humanize_list(&contact.projects[project_index].visible_worktree_root_names)
163            ),
164            waiting: true,
165            client,
166            _join_task,
167        }
168    }
169}
170
171fn humanize_list<'a>(items: impl IntoIterator<Item = &'a String>) -> String {
172    let mut list = String::new();
173    let mut items = items.into_iter().enumerate().peekable();
174    while let Some((ix, item)) = items.next() {
175        if ix > 0 {
176            list.push_str(", ");
177            if items.peek().is_none() {
178                list.push_str("and ");
179            }
180        }
181
182        list.push_str(item);
183    }
184    list
185}