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