notifications.rs

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