1use crate::notification_window_options;
2use call::{room, ActiveCall};
3use client::User;
4use collections::HashMap;
5use gpui::{
6 elements::*,
7 geometry::vector::vec2f,
8 platform::{CursorStyle, MouseButton},
9 AppContext, Entity, View, ViewContext,
10};
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 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 window =
29 cx.add_window(notification_window_options(screen, window_size), |_| {
30 ProjectSharedNotification::new(
31 owner.clone(),
32 *project_id,
33 worktree_root_names.clone(),
34 app_state.clone(),
35 )
36 });
37 notification_windows
38 .entry(*project_id)
39 .or_insert(Vec::new())
40 .push(window);
41 }
42 }
43 room::Event::RemoteProjectUnshared { project_id }
44 | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
45 if let Some(windows) = notification_windows.remove(&project_id) {
46 for window in windows {
47 window.remove(cx);
48 }
49 }
50 }
51 room::Event::Left => {
52 for (_, windows) in notification_windows.drain() {
53 for window in windows {
54 window.remove(cx);
55 }
56 }
57 }
58 room::Event::RemoteProjectJoined { project_id } => {
59 if let Some(windows) = notification_windows.remove(&project_id) {
60 for window in windows {
61 window.remove(cx);
62 }
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 app_state: Weak<AppState>,
75}
76
77impl ProjectSharedNotification {
78 fn new(
79 owner: Arc<User>,
80 project_id: u64,
81 worktree_root_names: Vec<String>,
82 app_state: Weak<AppState>,
83 ) -> Self {
84 Self {
85 project_id,
86 worktree_root_names,
87 owner,
88 app_state,
89 }
90 }
91
92 fn join(&mut self, cx: &mut ViewContext<Self>) {
93 if let Some(app_state) = self.app_state.upgrade() {
94 workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
95 .detach_and_log_err(cx);
96 }
97 }
98
99 fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
100 if let Some(active_room) =
101 ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
102 {
103 active_room.update(cx, |_, cx| {
104 cx.emit(room::Event::RemoteProjectInvitationDiscarded {
105 project_id: self.project_id,
106 });
107 });
108 }
109 }
110
111 fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
112 let theme = &theme::current(cx).project_shared_notification;
113 Flex::row()
114 .with_children(self.owner.avatar.clone().map(|avatar| {
115 Image::from_data(avatar)
116 .with_style(theme.owner_avatar)
117 .aligned()
118 }))
119 .with_child(
120 Flex::column()
121 .with_child(
122 Label::new(
123 self.owner.github_login.clone(),
124 theme.owner_username.text.clone(),
125 )
126 .contained()
127 .with_style(theme.owner_username.container),
128 )
129 .with_child(
130 Label::new(
131 format!(
132 "is sharing a project in Zed{}",
133 if self.worktree_root_names.is_empty() {
134 ""
135 } else {
136 ":"
137 }
138 ),
139 theme.message.text.clone(),
140 )
141 .contained()
142 .with_style(theme.message.container),
143 )
144 .with_children(if self.worktree_root_names.is_empty() {
145 None
146 } else {
147 Some(
148 Label::new(
149 self.worktree_root_names.join(", "),
150 theme.worktree_roots.text.clone(),
151 )
152 .contained()
153 .with_style(theme.worktree_roots.container),
154 )
155 })
156 .contained()
157 .with_style(theme.owner_metadata)
158 .aligned(),
159 )
160 .contained()
161 .with_style(theme.owner_container)
162 .flex(1., true)
163 .into_any()
164 }
165
166 fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
167 enum Open {}
168 enum Dismiss {}
169
170 let theme = theme::current(cx);
171 Flex::column()
172 .with_child(
173 MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
174 let theme = &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::new::<Dismiss, _>(0, cx, |_, _| {
186 let theme = &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(theme.project_shared_notification.button_width)
200 .into_any()
201 }
202}
203
204impl Entity for ProjectSharedNotification {
205 type Event = ();
206}
207
208impl View for ProjectSharedNotification {
209 fn ui_name() -> &'static str {
210 "ProjectSharedNotification"
211 }
212
213 fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
214 let background = theme::current(cx).project_shared_notification.background;
215 Flex::row()
216 .with_child(self.render_owner(cx))
217 .with_child(self.render_buttons(cx))
218 .contained()
219 .with_background_color(background)
220 .expanded()
221 .into_any()
222 }
223}