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