project_shared_notification.rs

  1use crate::notification_window_options;
  2use call::{room, ActiveCall};
  3use client::User;
  4use collections::HashMap;
  5use gpui::{
  6    elements::*,
  7    geometry::vector::vec2f,
  8    platform::{CursorStyle, MouseButton},
  9    AppContext, Entity, View, ViewContext,
 10};
 11use std::sync::{Arc, Weak};
 12use workspace::AppState;
 13
 14pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 15    let app_state = Arc::downgrade(app_state);
 16    let active_call = ActiveCall::global(cx);
 17    let mut notification_windows = HashMap::default();
 18    cx.subscribe(&active_call, move |_, event, cx| match event {
 19        room::Event::RemoteProjectShared {
 20            owner,
 21            project_id,
 22            worktree_root_names,
 23        } => {
 24            let theme = &theme::current(cx).project_shared_notification;
 25            let window_size = vec2f(theme.window_width, theme.window_height);
 26
 27            for screen in cx.platform().screens() {
 28                let window =
 29                    cx.add_window(notification_window_options(screen, window_size), |_| {
 30                        ProjectSharedNotification::new(
 31                            owner.clone(),
 32                            *project_id,
 33                            worktree_root_names.clone(),
 34                            app_state.clone(),
 35                        )
 36                    });
 37                notification_windows
 38                    .entry(*project_id)
 39                    .or_insert(Vec::new())
 40                    .push(window);
 41            }
 42        }
 43        room::Event::RemoteProjectUnshared { project_id }
 44        | room::Event::RemoteProjectJoined { project_id }
 45        | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
 46            if let Some(windows) = notification_windows.remove(&project_id) {
 47                for window in windows {
 48                    window.remove(cx);
 49                }
 50            }
 51        }
 52        room::Event::Left => {
 53            for (_, windows) in notification_windows.drain() {
 54                for window in windows {
 55                    window.remove(cx);
 56                }
 57            }
 58        }
 59        _ => {}
 60    })
 61    .detach();
 62}
 63
 64pub struct ProjectSharedNotification {
 65    project_id: u64,
 66    worktree_root_names: Vec<String>,
 67    owner: Arc<User>,
 68    app_state: Weak<AppState>,
 69}
 70
 71impl ProjectSharedNotification {
 72    fn new(
 73        owner: Arc<User>,
 74        project_id: u64,
 75        worktree_root_names: Vec<String>,
 76        app_state: Weak<AppState>,
 77    ) -> Self {
 78        Self {
 79            project_id,
 80            worktree_root_names,
 81            owner,
 82            app_state,
 83        }
 84    }
 85
 86    fn join(&mut self, cx: &mut ViewContext<Self>) {
 87        if let Some(app_state) = self.app_state.upgrade() {
 88            workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
 89                .detach_and_log_err(cx);
 90        }
 91    }
 92
 93    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
 94        if let Some(active_room) =
 95            ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
 96        {
 97            active_room.update(cx, |_, cx| {
 98                cx.emit(room::Event::RemoteProjectInvitationDiscarded {
 99                    project_id: self.project_id,
100                });
101            });
102        }
103    }
104
105    fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
106        let theme = &theme::current(cx).project_shared_notification;
107        Flex::row()
108            .with_children(self.owner.avatar.clone().map(|avatar| {
109                Image::from_data(avatar)
110                    .with_style(theme.owner_avatar)
111                    .aligned()
112            }))
113            .with_child(
114                Flex::column()
115                    .with_child(
116                        Label::new(
117                            self.owner.github_login.clone(),
118                            theme.owner_username.text.clone(),
119                        )
120                        .contained()
121                        .with_style(theme.owner_username.container),
122                    )
123                    .with_child(
124                        Label::new(
125                            format!(
126                                "is sharing a project in Zed{}",
127                                if self.worktree_root_names.is_empty() {
128                                    ""
129                                } else {
130                                    ":"
131                                }
132                            ),
133                            theme.message.text.clone(),
134                        )
135                        .contained()
136                        .with_style(theme.message.container),
137                    )
138                    .with_children(if self.worktree_root_names.is_empty() {
139                        None
140                    } else {
141                        Some(
142                            Label::new(
143                                self.worktree_root_names.join(", "),
144                                theme.worktree_roots.text.clone(),
145                            )
146                            .contained()
147                            .with_style(theme.worktree_roots.container),
148                        )
149                    })
150                    .contained()
151                    .with_style(theme.owner_metadata)
152                    .aligned(),
153            )
154            .contained()
155            .with_style(theme.owner_container)
156            .flex(1., true)
157            .into_any()
158    }
159
160    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
161        enum Open {}
162        enum Dismiss {}
163
164        let theme = theme::current(cx);
165        Flex::column()
166            .with_child(
167                MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
168                    let theme = &theme.project_shared_notification;
169                    Label::new("Open", theme.open_button.text.clone())
170                        .aligned()
171                        .contained()
172                        .with_style(theme.open_button.container)
173                })
174                .with_cursor_style(CursorStyle::PointingHand)
175                .on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
176                .flex(1., true),
177            )
178            .with_child(
179                MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
180                    let theme = &theme.project_shared_notification;
181                    Label::new("Dismiss", theme.dismiss_button.text.clone())
182                        .aligned()
183                        .contained()
184                        .with_style(theme.dismiss_button.container)
185                })
186                .with_cursor_style(CursorStyle::PointingHand)
187                .on_click(MouseButton::Left, |_, this, cx| {
188                    this.dismiss(cx);
189                })
190                .flex(1., true),
191            )
192            .constrained()
193            .with_width(theme.project_shared_notification.button_width)
194            .into_any()
195    }
196}
197
198impl Entity for ProjectSharedNotification {
199    type Event = ();
200}
201
202impl View for ProjectSharedNotification {
203    fn ui_name() -> &'static str {
204        "ProjectSharedNotification"
205    }
206
207    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
208        let background = theme::current(cx).project_shared_notification.background;
209        Flex::row()
210            .with_child(self.render_owner(cx))
211            .with_child(self.render_buttons(cx))
212            .contained()
213            .with_background_color(background)
214            .expanded()
215            .into_any()
216    }
217}