waiting_room.rs

  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}