1use crate::notification_window_options;
2use crate::notifications::collab_notification::CollabNotification;
3use call::{room, ActiveCall};
4use client::User;
5use collections::HashMap;
6use gpui::{AppContext, Size};
7use settings::Settings;
8use std::sync::{Arc, Weak};
9use theme::ThemeSettings;
10use ui::{prelude::*, Button, Label};
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 let window_size = Size {
24 width: px(400.),
25 height: px(72.),
26 };
27
28 for screen in cx.displays() {
29 let options = notification_window_options(screen, window_size, cx);
30 let window = cx.open_window(options, |cx| {
31 cx.new_view(|_| {
32 ProjectSharedNotification::new(
33 owner.clone(),
34 *project_id,
35 worktree_root_names.clone(),
36 app_state.clone(),
37 )
38 })
39 });
40 notification_windows
41 .entry(*project_id)
42 .or_insert(Vec::new())
43 .push(window);
44 }
45 }
46
47 room::Event::RemoteProjectUnshared { project_id }
48 | room::Event::RemoteProjectJoined { project_id }
49 | room::Event::RemoteProjectInvitationDiscarded { project_id } => {
50 if let Some(windows) = notification_windows.remove(&project_id) {
51 for window in windows {
52 window
53 .update(cx, |_, cx| {
54 cx.remove_window();
55 })
56 .ok();
57 }
58 }
59 }
60
61 room::Event::RoomLeft { .. } => {
62 for (_, windows) in notification_windows.drain() {
63 for window in windows {
64 window
65 .update(cx, |_, cx| {
66 cx.remove_window();
67 })
68 .ok();
69 }
70 }
71 }
72 _ => {}
73 })
74 .detach();
75}
76
77pub struct ProjectSharedNotification {
78 project_id: u64,
79 worktree_root_names: Vec<String>,
80 owner: Arc<User>,
81 app_state: Weak<AppState>,
82}
83
84impl ProjectSharedNotification {
85 fn new(
86 owner: Arc<User>,
87 project_id: u64,
88 worktree_root_names: Vec<String>,
89 app_state: Weak<AppState>,
90 ) -> Self {
91 Self {
92 project_id,
93 worktree_root_names,
94 owner,
95 app_state,
96 }
97 }
98
99 fn join(&mut self, cx: &mut ViewContext<Self>) {
100 if let Some(app_state) = self.app_state.upgrade() {
101 workspace::join_in_room_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 if let Some(active_room) =
108 ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
109 {
110 active_room.update(cx, |_, cx| {
111 cx.emit(room::Event::RemoteProjectInvitationDiscarded {
112 project_id: self.project_id,
113 });
114 });
115 }
116 }
117}
118
119impl Render for ProjectSharedNotification {
120 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
121 // TODO: Is there a better place for us to initialize the font?
122 let (ui_font, ui_font_size) = {
123 let theme_settings = ThemeSettings::get_global(cx);
124 (theme_settings.ui_font.clone(), theme_settings.ui_font_size)
125 };
126
127 cx.set_rem_size(ui_font_size);
128
129 div().size_full().font(ui_font).child(
130 CollabNotification::new(
131 self.owner.avatar_uri.clone(),
132 Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
133 this.join(cx);
134 })),
135 Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
136 this.dismiss(cx);
137 })),
138 )
139 .child(Label::new(self.owner.github_login.clone()))
140 .child(Label::new(format!(
141 "is sharing a project in Zed{}",
142 if self.worktree_root_names.is_empty() {
143 ""
144 } else {
145 ":"
146 }
147 )))
148 .children(if self.worktree_root_names.is_empty() {
149 None
150 } else {
151 Some(Label::new(self.worktree_root_names.join(", ")))
152 }),
153 )
154 }
155}