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