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 ¬ification,
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}