project_shared_notification.rs

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