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}