project_shared_notification.rs

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