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}