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