1use crate::{Toast, Workspace};
2use collections::HashMap;
3use gpui::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext};
4use std::{any::TypeId, ops::DerefMut};
5
6pub fn init(cx: &mut AppContext) {
7 cx.set_global(NotificationTracker::new());
8 // todo!()
9 // simple_message_notification::init(cx);
10}
11
12pub trait Notification: EventEmitter + Render {
13 fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool;
14}
15
16pub trait NotificationHandle: Send {
17 fn id(&self) -> EntityId;
18 fn to_any(&self) -> AnyView;
19}
20
21impl<T: Notification> NotificationHandle for View<T> {
22 fn id(&self) -> EntityId {
23 self.entity_id()
24 }
25
26 fn to_any(&self) -> AnyView {
27 self.clone().into()
28 }
29}
30
31impl From<&dyn NotificationHandle> for AnyView {
32 fn from(val: &dyn NotificationHandle) -> Self {
33 val.to_any()
34 }
35}
36
37pub(crate) struct NotificationTracker {
38 notifications_sent: HashMap<TypeId, Vec<usize>>,
39}
40
41impl std::ops::Deref for NotificationTracker {
42 type Target = HashMap<TypeId, Vec<usize>>;
43
44 fn deref(&self) -> &Self::Target {
45 &self.notifications_sent
46 }
47}
48
49impl DerefMut for NotificationTracker {
50 fn deref_mut(&mut self) -> &mut Self::Target {
51 &mut self.notifications_sent
52 }
53}
54
55impl NotificationTracker {
56 fn new() -> Self {
57 Self {
58 notifications_sent: Default::default(),
59 }
60 }
61}
62
63impl Workspace {
64 pub fn has_shown_notification_once<V: Notification>(
65 &self,
66 id: usize,
67 cx: &ViewContext<Self>,
68 ) -> bool {
69 cx.global::<NotificationTracker>()
70 .get(&TypeId::of::<V>())
71 .map(|ids| ids.contains(&id))
72 .unwrap_or(false)
73 }
74
75 pub fn show_notification_once<V: Notification>(
76 &mut self,
77 id: usize,
78 cx: &mut ViewContext<Self>,
79 build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
80 ) {
81 if !self.has_shown_notification_once::<V>(id, cx) {
82 let tracker = cx.global_mut::<NotificationTracker>();
83 let entry = tracker.entry(TypeId::of::<V>()).or_default();
84 entry.push(id);
85 self.show_notification::<V>(id, cx, build_notification)
86 }
87 }
88
89 pub fn show_notification<V: Notification>(
90 &mut self,
91 id: usize,
92 cx: &mut ViewContext<Self>,
93 build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
94 ) {
95 let type_id = TypeId::of::<V>();
96 if self
97 .notifications
98 .iter()
99 .all(|(existing_type_id, existing_id, _)| {
100 (*existing_type_id, *existing_id) != (type_id, id)
101 })
102 {
103 let notification = build_notification(cx);
104 cx.subscribe(¬ification, move |this, handle, event, cx| {
105 if handle.read(cx).should_dismiss_notification_on_event(event) {
106 this.dismiss_notification_internal(type_id, id, cx);
107 }
108 })
109 .detach();
110 self.notifications
111 .push((type_id, id, Box::new(notification)));
112 cx.notify();
113 }
114 }
115
116 pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
117 let type_id = TypeId::of::<V>();
118
119 self.dismiss_notification_internal(type_id, id, cx)
120 }
121
122 pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
123 todo!()
124 // self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
125 // self.show_notification(toast.id, cx, |cx| {
126 // cx.add_view(|_cx| match toast.on_click.as_ref() {
127 // Some((click_msg, on_click)) => {
128 // let on_click = on_click.clone();
129 // simple_message_notification::MessageNotification::new(toast.msg.clone())
130 // .with_click_message(click_msg.clone())
131 // .on_click(move |cx| on_click(cx))
132 // }
133 // None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
134 // })
135 // })
136 }
137
138 pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
139 todo!()
140 // self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
141 }
142
143 fn dismiss_notification_internal(
144 &mut self,
145 type_id: TypeId,
146 id: usize,
147 cx: &mut ViewContext<Self>,
148 ) {
149 self.notifications
150 .retain(|(existing_type_id, existing_id, _)| {
151 if (*existing_type_id, *existing_id) == (type_id, id) {
152 cx.notify();
153 false
154 } else {
155 true
156 }
157 });
158 }
159}
160
161pub mod simple_message_notification {
162 use super::Notification;
163 use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
164 use serde::Deserialize;
165 use std::{borrow::Cow, sync::Arc};
166
167 // todo!()
168 // actions!(message_notifications, [CancelMessageNotification]);
169
170 #[derive(Clone, Default, Deserialize, PartialEq)]
171 pub struct OsOpen(pub Cow<'static, str>);
172
173 impl OsOpen {
174 pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
175 OsOpen(url.into())
176 }
177 }
178
179 // todo!()
180 // impl_actions!(message_notifications, [OsOpen]);
181 //
182 // todo!()
183 // pub fn init(cx: &mut AppContext) {
184 // cx.add_action(MessageNotification::dismiss);
185 // cx.add_action(
186 // |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
187 // cx.platform().open_url(open_action.0.as_ref());
188 // },
189 // )
190 // }
191
192 enum NotificationMessage {
193 Text(Cow<'static, str>),
194 Element(fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>),
195 }
196
197 pub struct MessageNotification {
198 message: NotificationMessage,
199 on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
200 click_message: Option<Cow<'static, str>>,
201 }
202
203 pub enum MessageNotificationEvent {
204 Dismiss,
205 }
206
207 impl EventEmitter for MessageNotification {
208 type Event = MessageNotificationEvent;
209 }
210
211 impl MessageNotification {
212 pub fn new<S>(message: S) -> MessageNotification
213 where
214 S: Into<Cow<'static, str>>,
215 {
216 Self {
217 message: NotificationMessage::Text(message.into()),
218 on_click: None,
219 click_message: None,
220 }
221 }
222
223 pub fn new_element(
224 message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
225 ) -> MessageNotification {
226 Self {
227 message: NotificationMessage::Element(message),
228 on_click: None,
229 click_message: None,
230 }
231 }
232
233 pub fn with_click_message<S>(mut self, message: S) -> Self
234 where
235 S: Into<Cow<'static, str>>,
236 {
237 self.click_message = Some(message.into());
238 self
239 }
240
241 pub fn on_click<F>(mut self, on_click: F) -> Self
242 where
243 F: 'static + Send + Sync + Fn(&mut ViewContext<Self>),
244 {
245 self.on_click = Some(Arc::new(on_click));
246 self
247 }
248
249 // todo!()
250 // pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
251 // cx.emit(MessageNotificationEvent::Dismiss);
252 // }
253 }
254
255 impl Render for MessageNotification {
256 type Element = Div<Self>;
257
258 fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
259 todo!()
260 }
261 }
262 // todo!()
263 // impl View for MessageNotification {
264 // fn ui_name() -> &'static str {
265 // "MessageNotification"
266 // }
267
268 // fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
269 // let theme = theme2::current(cx).clone();
270 // let theme = &theme.simple_message_notification;
271
272 // enum MessageNotificationTag {}
273
274 // let click_message = self.click_message.clone();
275 // let message = match &self.message {
276 // NotificationMessage::Text(text) => {
277 // Text::new(text.to_owned(), theme.message.text.clone()).into_any()
278 // }
279 // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
280 // };
281 // let on_click = self.on_click.clone();
282 // let has_click_action = on_click.is_some();
283
284 // Flex::column()
285 // .with_child(
286 // Flex::row()
287 // .with_child(
288 // message
289 // .contained()
290 // .with_style(theme.message.container)
291 // .aligned()
292 // .top()
293 // .left()
294 // .flex(1., true),
295 // )
296 // .with_child(
297 // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
298 // let style = theme.dismiss_button.style_for(state);
299 // Svg::new("icons/x.svg")
300 // .with_color(style.color)
301 // .constrained()
302 // .with_width(style.icon_width)
303 // .aligned()
304 // .contained()
305 // .with_style(style.container)
306 // .constrained()
307 // .with_width(style.button_width)
308 // .with_height(style.button_width)
309 // })
310 // .with_padding(Padding::uniform(5.))
311 // .on_click(MouseButton::Left, move |_, this, cx| {
312 // this.dismiss(&Default::default(), cx);
313 // })
314 // .with_cursor_style(CursorStyle::PointingHand)
315 // .aligned()
316 // .constrained()
317 // .with_height(cx.font_cache().line_height(theme.message.text.font_size))
318 // .aligned()
319 // .top()
320 // .flex_float(),
321 // ),
322 // )
323 // .with_children({
324 // click_message
325 // .map(|click_message| {
326 // MouseEventHandler::new::<MessageNotificationTag, _>(
327 // 0,
328 // cx,
329 // |state, _| {
330 // let style = theme.action_message.style_for(state);
331
332 // Flex::row()
333 // .with_child(
334 // Text::new(click_message, style.text.clone())
335 // .contained()
336 // .with_style(style.container),
337 // )
338 // .contained()
339 // },
340 // )
341 // .on_click(MouseButton::Left, move |_, this, cx| {
342 // if let Some(on_click) = on_click.as_ref() {
343 // on_click(cx);
344 // this.dismiss(&Default::default(), cx);
345 // }
346 // })
347 // // Since we're not using a proper overlay, we have to capture these extra events
348 // .on_down(MouseButton::Left, |_, _, _| {})
349 // .on_up(MouseButton::Left, |_, _, _| {})
350 // .with_cursor_style(if has_click_action {
351 // CursorStyle::PointingHand
352 // } else {
353 // CursorStyle::Arrow
354 // })
355 // })
356 // .into_iter()
357 // })
358 // .into_any()
359 // }
360 // }
361
362 impl Notification for MessageNotification {
363 fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool {
364 match event {
365 MessageNotificationEvent::Dismiss => true,
366 }
367 }
368 }
369}
370
371pub trait NotifyResultExt {
372 type Ok;
373
374 fn notify_err(
375 self,
376 workspace: &mut Workspace,
377 cx: &mut ViewContext<Workspace>,
378 ) -> Option<Self::Ok>;
379}
380
381impl<T, E> NotifyResultExt for Result<T, E>
382where
383 E: std::fmt::Debug,
384{
385 type Ok = T;
386
387 fn notify_err(self, workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<T> {
388 match self {
389 Ok(value) => Some(value),
390 Err(err) => {
391 log::error!("TODO {err:?}");
392 // todo!()
393 // workspace.show_notification(0, cx, |cx| {
394 // cx.add_view(|_cx| {
395 // simple_message_notification::MessageNotification::new(format!(
396 // "Error: {err:?}",
397 // ))
398 // })
399 // });
400 None
401 }
402 }
403 }
404}