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::RemoteProjectJoined { project_id }
45 | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
46 if let Some(windows) = notification_windows.remove(&project_id) {
47 for window in windows {
48 window.remove(cx);
49 }
50 }
51 }
52 room::Event::Left => {
53 for (_, windows) in notification_windows.drain() {
54 for window in windows {
55 window.remove(cx);
56 }
57 }
58 }
59 _ => {}
60 })
61 .detach();
62}
63
64pub struct ProjectSharedNotification {
65 project_id: u64,
66 worktree_root_names: Vec<String>,
67 owner: Arc<User>,
68 app_state: Weak<AppState>,
69}
70
71impl ProjectSharedNotification {
72 fn new(
73 owner: Arc<User>,
74 project_id: u64,
75 worktree_root_names: Vec<String>,
76 app_state: Weak<AppState>,
77 ) -> Self {
78 Self {
79 project_id,
80 worktree_root_names,
81 owner,
82 app_state,
83 }
84 }
85
86 fn join(&mut self, cx: &mut ViewContext<Self>) {
87 if let Some(app_state) = self.app_state.upgrade() {
88 workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
89 .detach_and_log_err(cx);
90 }
91 }
92
93 fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
94 if let Some(active_room) =
95 ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
96 {
97 active_room.update(cx, |_, cx| {
98 cx.emit(room::Event::RemoteProjectInvitationDiscarded {
99 project_id: self.project_id,
100 });
101 });
102 }
103 }
104
105 fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
106 let theme = &theme::current(cx).project_shared_notification;
107 Flex::row()
108 .with_children(self.owner.avatar.clone().map(|avatar| {
109 Image::from_data(avatar)
110 .with_style(theme.owner_avatar)
111 .aligned()
112 }))
113 .with_child(
114 Flex::column()
115 .with_child(
116 Label::new(
117 self.owner.github_login.clone(),
118 theme.owner_username.text.clone(),
119 )
120 .contained()
121 .with_style(theme.owner_username.container),
122 )
123 .with_child(
124 Label::new(
125 format!(
126 "is sharing a project in Zed{}",
127 if self.worktree_root_names.is_empty() {
128 ""
129 } else {
130 ":"
131 }
132 ),
133 theme.message.text.clone(),
134 )
135 .contained()
136 .with_style(theme.message.container),
137 )
138 .with_children(if self.worktree_root_names.is_empty() {
139 None
140 } else {
141 Some(
142 Label::new(
143 self.worktree_root_names.join(", "),
144 theme.worktree_roots.text.clone(),
145 )
146 .contained()
147 .with_style(theme.worktree_roots.container),
148 )
149 })
150 .contained()
151 .with_style(theme.owner_metadata)
152 .aligned(),
153 )
154 .contained()
155 .with_style(theme.owner_container)
156 .flex(1., true)
157 .into_any()
158 }
159
160 fn render_buttons(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
161 enum Open {}
162 enum Dismiss {}
163
164 let theme = theme::current(cx);
165 Flex::column()
166 .with_child(
167 MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
168 let theme = &theme.project_shared_notification;
169 Label::new("Open", theme.open_button.text.clone())
170 .aligned()
171 .contained()
172 .with_style(theme.open_button.container)
173 })
174 .with_cursor_style(CursorStyle::PointingHand)
175 .on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
176 .flex(1., true),
177 )
178 .with_child(
179 MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
180 let theme = &theme.project_shared_notification;
181 Label::new("Dismiss", theme.dismiss_button.text.clone())
182 .aligned()
183 .contained()
184 .with_style(theme.dismiss_button.container)
185 })
186 .with_cursor_style(CursorStyle::PointingHand)
187 .on_click(MouseButton::Left, |_, this, cx| {
188 this.dismiss(cx);
189 })
190 .flex(1., true),
191 )
192 .constrained()
193 .with_width(theme.project_shared_notification.button_width)
194 .into_any()
195 }
196}
197
198impl Entity for ProjectSharedNotification {
199 type Event = ();
200}
201
202impl View for ProjectSharedNotification {
203 fn ui_name() -> &'static str {
204 "ProjectSharedNotification"
205 }
206
207 fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
208 let background = theme::current(cx).project_shared_notification.background;
209 Flex::row()
210 .with_child(self.render_owner(cx))
211 .with_child(self.render_buttons(cx))
212 .contained()
213 .with_background_color(background)
214 .expanded()
215 .into_any()
216 }
217}