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