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