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 screen_size = cx.platform().screen_size();
 31
 32            let theme = &cx.global::<Settings>().theme.project_shared_notification;
 33            let window_size = vec2f(theme.window_width, theme.window_height);
 34            let (window_id, _) = cx.add_window(
 35                WindowOptions {
 36                    bounds: WindowBounds::Fixed(RectF::new(
 37                        vec2f(screen_size.x() - window_size.x() - PADDING, PADDING),
 38                        window_size,
 39                    )),
 40                    titlebar: None,
 41                    center: false,
 42                    kind: WindowKind::PopUp,
 43                    is_movable: false,
 44                },
 45                |_| {
 46                    ProjectSharedNotification::new(
 47                        owner.clone(),
 48                        *project_id,
 49                        worktree_root_names.clone(),
 50                    )
 51                },
 52            );
 53            notification_windows.insert(*project_id, window_id);
 54        }
 55        room::Event::RemoteProjectUnshared { project_id } => {
 56            if let Some(window_id) = notification_windows.remove(&project_id) {
 57                cx.remove_window(window_id);
 58            }
 59        }
 60        room::Event::Left => {
 61            for (_, window_id) in notification_windows.drain() {
 62                cx.remove_window(window_id);
 63            }
 64        }
 65        _ => {}
 66    })
 67    .detach();
 68}
 69
 70pub struct ProjectSharedNotification {
 71    project_id: u64,
 72    worktree_root_names: Vec<String>,
 73    owner: Arc<User>,
 74}
 75
 76impl ProjectSharedNotification {
 77    fn new(owner: Arc<User>, project_id: u64, worktree_root_names: Vec<String>) -> Self {
 78        Self {
 79            project_id,
 80            worktree_root_names,
 81            owner,
 82        }
 83    }
 84
 85    fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
 86        let window_id = cx.window_id();
 87        cx.remove_window(window_id);
 88        cx.propagate_action();
 89    }
 90
 91    fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
 92        let window_id = cx.window_id();
 93        cx.remove_window(window_id);
 94    }
 95
 96    fn render_owner(&self, cx: &mut RenderContext<Self>) -> ElementBox {
 97        let theme = &cx.global::<Settings>().theme.project_shared_notification;
 98        Flex::row()
 99            .with_children(self.owner.avatar.clone().map(|avatar| {
100                Image::new(avatar)
101                    .with_style(theme.owner_avatar)
102                    .aligned()
103                    .boxed()
104            }))
105            .with_child(
106                Flex::column()
107                    .with_child(
108                        Label::new(
109                            self.owner.github_login.clone(),
110                            theme.owner_username.text.clone(),
111                        )
112                        .contained()
113                        .with_style(theme.owner_username.container)
114                        .boxed(),
115                    )
116                    .with_child(
117                        Label::new(
118                            format!(
119                                "is sharing a project in Zed{}",
120                                if self.worktree_root_names.is_empty() {
121                                    ""
122                                } else {
123                                    ":"
124                                }
125                            ),
126                            theme.message.text.clone(),
127                        )
128                        .contained()
129                        .with_style(theme.message.container)
130                        .boxed(),
131                    )
132                    .with_children(if self.worktree_root_names.is_empty() {
133                        None
134                    } else {
135                        Some(
136                            Label::new(
137                                self.worktree_root_names.join(", "),
138                                theme.worktree_roots.text.clone(),
139                            )
140                            .contained()
141                            .with_style(theme.worktree_roots.container)
142                            .boxed(),
143                        )
144                    })
145                    .contained()
146                    .with_style(theme.owner_metadata)
147                    .aligned()
148                    .boxed(),
149            )
150            .contained()
151            .with_style(theme.owner_container)
152            .flex(1., true)
153            .boxed()
154    }
155
156    fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
157        enum Open {}
158        enum Dismiss {}
159
160        let project_id = self.project_id;
161        let owner_user_id = self.owner.id;
162
163        Flex::column()
164            .with_child(
165                MouseEventHandler::<Open>::new(0, cx, |_, cx| {
166                    let theme = &cx.global::<Settings>().theme.project_shared_notification;
167                    Label::new("Open".to_string(), theme.open_button.text.clone())
168                        .aligned()
169                        .contained()
170                        .with_style(theme.open_button.container)
171                        .boxed()
172                })
173                .with_cursor_style(CursorStyle::PointingHand)
174                .on_click(MouseButton::Left, move |_, cx| {
175                    cx.dispatch_action(JoinProject {
176                        project_id,
177                        follow_user_id: owner_user_id,
178                    });
179                })
180                .flex(1., true)
181                .boxed(),
182            )
183            .with_child(
184                MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| {
185                    let theme = &cx.global::<Settings>().theme.project_shared_notification;
186                    Label::new("Dismiss".to_string(), theme.dismiss_button.text.clone())
187                        .aligned()
188                        .contained()
189                        .with_style(theme.dismiss_button.container)
190                        .boxed()
191                })
192                .with_cursor_style(CursorStyle::PointingHand)
193                .on_click(MouseButton::Left, |_, cx| {
194                    cx.dispatch_action(DismissProject);
195                })
196                .flex(1., true)
197                .boxed(),
198            )
199            .constrained()
200            .with_width(
201                cx.global::<Settings>()
202                    .theme
203                    .project_shared_notification
204                    .button_width,
205            )
206            .boxed()
207    }
208}
209
210impl Entity for ProjectSharedNotification {
211    type Event = ();
212}
213
214impl View for ProjectSharedNotification {
215    fn ui_name() -> &'static str {
216        "ProjectSharedNotification"
217    }
218
219    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
220        let background = cx
221            .global::<Settings>()
222            .theme
223            .project_shared_notification
224            .background;
225        Flex::row()
226            .with_child(self.render_owner(cx))
227            .with_child(self.render_buttons(cx))
228            .contained()
229            .with_background_color(background)
230            .expanded()
231            .boxed()
232    }
233}