project_shared_notification.rs

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