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            if let Some(windows) = notification_windows.remove(&project_id) {
 45                for window in windows {
 46                    window.remove(cx);
 47                }
 48            }
 49        }
 50        room::Event::Left => {
 51            for (_, windows) in notification_windows.drain() {
 52                for window in windows {
 53                    window.remove(cx);
 54                }
 55            }
 56        }
 57        _ => {}
 58    })
 59    .detach();
 60}
 61
 62pub struct ProjectSharedNotification {
 63    project_id: u64,
 64    worktree_root_names: Vec<String>,
 65    owner: Arc<User>,
 66    app_state: Weak<AppState>,
 67}
 68
 69impl ProjectSharedNotification {
 70    fn new(
 71        owner: Arc<User>,
 72        project_id: u64,
 73        worktree_root_names: Vec<String>,
 74        app_state: Weak<AppState>,
 75    ) -> Self {
 76        Self {
 77            project_id,
 78            worktree_root_names,
 79            owner,
 80            app_state,
 81        }
 82    }
 83
 84    fn join(&mut self, cx: &mut ViewContext<Self>) {
 85        cx.remove_window();
 86        if let Some(app_state) = self.app_state.upgrade() {
 87            workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
 88                .detach_and_log_err(cx);
 89        }
 90    }
 91
 92    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
 93        cx.remove_window();
 94    }
 95
 96    fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
 97        let theme = &theme::current(cx).project_shared_notification;
 98        Flex::row()
 99            .with_children(self.owner.avatar.clone().map(|avatar| {
100                Image::from_data(avatar)
101                    .with_style(theme.owner_avatar)
102                    .aligned()
103            }))
104            .with_child(
105                Flex::column()
106                    .with_child(
107                        Label::new(
108                            self.owner.github_login.clone(),
109                            theme.owner_username.text.clone(),
110                        )
111                        .contained()
112                        .with_style(theme.owner_username.container),
113                    )
114                    .with_child(
115                        Label::new(
116                            format!(
117                                "is sharing a project in Zed{}",
118                                if self.worktree_root_names.is_empty() {
119                                    ""
120                                } else {
121                                    ":"
122                                }
123                            ),
124                            theme.message.text.clone(),
125                        )
126                        .contained()
127                        .with_style(theme.message.container),
128                    )
129                    .with_children(if self.worktree_root_names.is_empty() {
130                        None
131                    } else {
132                        Some(
133                            Label::new(
134                                self.worktree_root_names.join(", "),
135                                theme.worktree_roots.text.clone(),
136                            )
137                            .contained()
138                            .with_style(theme.worktree_roots.container),
139                        )
140                    })
141                    .contained()
142                    .with_style(theme.owner_metadata)
143                    .aligned(),
144            )
145            .contained()
146            .with_style(theme.owner_container)
147            .flex(1., true)
148            .into_any()
149    }
150
151    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
152        enum Open {}
153        enum Dismiss {}
154
155        let theme = theme::current(cx);
156        Flex::column()
157            .with_child(
158                MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
159                    let theme = &theme.project_shared_notification;
160                    Label::new("Open", theme.open_button.text.clone())
161                        .aligned()
162                        .contained()
163                        .with_style(theme.open_button.container)
164                })
165                .with_cursor_style(CursorStyle::PointingHand)
166                .on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
167                .flex(1., true),
168            )
169            .with_child(
170                MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
171                    let theme = &theme.project_shared_notification;
172                    Label::new("Dismiss", theme.dismiss_button.text.clone())
173                        .aligned()
174                        .contained()
175                        .with_style(theme.dismiss_button.container)
176                })
177                .with_cursor_style(CursorStyle::PointingHand)
178                .on_click(MouseButton::Left, |_, this, cx| {
179                    this.dismiss(cx);
180                })
181                .flex(1., true),
182            )
183            .constrained()
184            .with_width(theme.project_shared_notification.button_width)
185            .into_any()
186    }
187}
188
189impl Entity for ProjectSharedNotification {
190    type Event = ();
191}
192
193impl View for ProjectSharedNotification {
194    fn ui_name() -> &'static str {
195        "ProjectSharedNotification"
196    }
197
198    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
199        let background = theme::current(cx).project_shared_notification.background;
200        Flex::row()
201            .with_child(self.render_owner(cx))
202            .with_child(self.render_buttons(cx))
203            .contained()
204            .with_background_color(background)
205            .expanded()
206            .into_any()
207    }
208}