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