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