Builds and runs

Julia created

Change summary

Cargo.lock                                    |   1 
crates/collab_ui2/src/collab_titlebar_item.rs |   5 
crates/collab_ui2/src/face_pile.rs            |   3 
crates/collab_ui2/src/notification_panel.rs   | 189 ++++++++++----------
crates/zed2/Cargo.toml                        |   1 
crates/zed2/src/main.rs                       |   1 
crates/zed2/src/zed2.rs                       |   6 
7 files changed, 108 insertions(+), 98 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -12046,6 +12046,7 @@ dependencies = [
  "lsp2",
  "menu2",
  "node_runtime",
+ "notifications2",
  "num_cpus",
  "outline2",
  "parking_lot 0.11.2",

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -422,8 +422,8 @@ impl CollabTitlebarItem {
         current_user: &Arc<User>,
     ) -> Option<FacePile> {
         let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
-        let mut pile = FacePile::default();
-        pile.child(
+
+        let pile = FacePile::default().child(
             div()
                 .child(
                     Avatar::new(user.avatar_uri.clone())
@@ -450,6 +450,7 @@ impl CollabTitlebarItem {
                     Some(div().child(Avatar::new(follower.avatar_uri.clone())))
                 })),
         );
+
         Some(pile)
     }
 

crates/collab_ui2/src/face_pile.rs 🔗

@@ -1,6 +1,5 @@
 use gpui::{
-    div, AnyElement, Div, ElementId, IntoElement, ParentElement as _, ParentElement, RenderOnce,
-    Styled, WindowContext,
+    div, AnyElement, Div, ElementId, IntoElement, ParentElement, RenderOnce, Styled, WindowContext,
 };
 use smallvec::SmallVec;
 

crates/collab_ui2/src/notification_panel.rs 🔗

@@ -6,11 +6,10 @@ use collections::HashMap;
 use db::kvp::KEY_VALUE_STORE;
 use futures::StreamExt;
 use gpui::{
-    actions, div, img, px, serde_json, svg, AnyElement, AnyView, AppContext, AsyncAppContext,
-    AsyncWindowContext, Context, CursorStyle, Div, Element, Entity, EventEmitter, Flatten,
-    FocusHandle, FocusableView, InteractiveElement, IntoElement, ListAlignment, ListScrollEvent,
-    ListState, Model, MouseButton, ParentElement, Render, Stateful, StatefulInteractiveElement,
-    Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    actions, div, px, serde_json, AnyElement, AppContext, AsyncWindowContext, DismissEvent, Div,
+    Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement,
+    ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render, Stateful,
+    StatefulInteractiveElement, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
 use project::Fs;
@@ -19,7 +18,10 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use std::{sync::Arc, time::Duration};
 use time::{OffsetDateTime, UtcOffset};
-use ui::{h_stack, v_stack, Avatar, Button, Clickable, Icon, IconButton, IconElement, Label, List};
+use ui::{
+    h_stack, v_stack, Avatar, Button, ButtonLike, Clickable, Disableable, Icon, IconButton,
+    IconElement, Label,
+};
 use util::{ResultExt, TryFutureExt};
 use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
@@ -98,7 +100,7 @@ impl NotificationPanel {
             })
             .detach();
 
-            let mut notification_list =
+            let notification_list =
                 ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
                     view.update(cx, |this, cx| {
                         this.render_notification(ix, cx)
@@ -220,58 +222,68 @@ impl NotificationPanel {
         }
 
         Some(
-            h_stack()
-                .children(actor.map(|actor| Avatar::new(actor.avatar_uri.clone())))
+            ButtonLike::new(ix)
                 .child(
-                    v_stack().child(Label::new(text)).child(
-                        h_stack()
-                            .child(Label::new(format_timestamp(
-                                timestamp,
-                                now,
-                                self.local_timezone,
-                            )))
-                            .children(if let Some(is_accepted) = response {
-                                Some(div().child(Label::new(if is_accepted {
-                                    "You accepted"
-                                } else {
-                                    "You declined"
-                                })))
-                            } else if needs_response {
-                                Some(
-                                    h_stack()
-                                        .child(Button::new("decline", "Decline").on_click({
-                                            let notification = notification.clone();
-                                            let view = cx.view().clone();
-                                            move |_, cx| {
-                                                view.update(cx, |this, cx| {
-                                                    this.respond_to_notification(
-                                                        notification.clone(),
-                                                        false,
-                                                        cx,
-                                                    )
-                                                });
-                                            }
-                                        }))
-                                        .child(Button::new("accept", "Accept").on_click({
-                                            let notification = notification.clone();
-                                            let view = cx.view().clone();
-                                            move |_, cx| {
-                                                view.update(cx, |this, cx| {
-                                                    this.respond_to_notification(
-                                                        notification.clone(),
-                                                        true,
-                                                        cx,
-                                                    )
-                                                });
-                                            }
-                                        })),
-                                )
-                            } else {
-                                None
-                            }),
-                    ),
+                    h_stack()
+                        .children(actor.map(|actor| Avatar::new(actor.avatar_uri.clone())))
+                        .child(
+                            v_stack().child(Label::new(text)).child(
+                                h_stack()
+                                    .child(Label::new(format_timestamp(
+                                        timestamp,
+                                        now,
+                                        self.local_timezone,
+                                    )))
+                                    .children(if let Some(is_accepted) = response {
+                                        Some(div().child(Label::new(if is_accepted {
+                                            "You accepted"
+                                        } else {
+                                            "You declined"
+                                        })))
+                                    } else if needs_response {
+                                        Some(
+                                            h_stack()
+                                                .child(Button::new("decline", "Decline").on_click(
+                                                    {
+                                                        let notification = notification.clone();
+                                                        let view = cx.view().clone();
+                                                        move |_, cx| {
+                                                            view.update(cx, |this, cx| {
+                                                                this.respond_to_notification(
+                                                                    notification.clone(),
+                                                                    false,
+                                                                    cx,
+                                                                )
+                                                            });
+                                                        }
+                                                    },
+                                                ))
+                                                .child(Button::new("accept", "Accept").on_click({
+                                                    let notification = notification.clone();
+                                                    let view = cx.view().clone();
+                                                    move |_, cx| {
+                                                        view.update(cx, |this, cx| {
+                                                            this.respond_to_notification(
+                                                                notification.clone(),
+                                                                true,
+                                                                cx,
+                                                            )
+                                                        });
+                                                    }
+                                                })),
+                                        )
+                                    } else {
+                                        None
+                                    }),
+                            ),
+                        ),
                 )
-                .into_any(),
+                .disabled(!can_navigate)
+                .on_click({
+                    let notification = notification.clone();
+                    cx.listener(move |this, _, cx| this.did_click_notification(&notification, cx))
+                })
+                .into_any_element(),
         )
     }
 
@@ -385,7 +397,7 @@ impl NotificationPanel {
         } = notification.clone()
         {
             if let Some(workspace) = self.workspace.upgrade() {
-                cx.app_context().defer(move |cx| {
+                cx.defer(move |_, cx| {
                     workspace.update(cx, |workspace, cx| {
                         if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
                             panel.update(cx, |panel, cx| {
@@ -400,37 +412,34 @@ impl NotificationPanel {
         }
     }
 
-    fn is_showing_notification(&self, notification: &Notification, cx: &AppContext) -> bool {
+    fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
         if let Notification::ChannelMessageMention { channel_id, .. } = &notification {
             if let Some(workspace) = self.workspace.upgrade() {
-                return workspace
-                    .read_with(cx, |workspace, cx| {
-                        if let Some(panel) = workspace.panel::<ChatPanel>(cx) {
-                            return panel.read_with(cx, |panel, cx| {
-                                panel.is_scrolled_to_bottom()
-                                    && panel.active_chat().map_or(false, |chat| {
-                                        chat.read(cx).channel_id == *channel_id
-                                    })
-                            });
-                        }
-                        false
-                    })
-                    .unwrap_or_default();
+                return if let Some(panel) = workspace.read(cx).panel::<ChatPanel>(cx) {
+                    let panel = panel.read(cx);
+                    panel.is_scrolled_to_bottom()
+                        && panel
+                            .active_chat()
+                            .map_or(false, |chat| chat.read(cx).channel_id == *channel_id)
+                } else {
+                    false
+                };
             }
         }
 
         false
     }
 
-    fn render_sign_in_prompt(&self, cx: &mut ViewContext<Self>) -> AnyElement {
+    fn render_sign_in_prompt(&self) -> AnyElement {
         Button::new(
             "sign_in_prompt_button",
             "Sign in to view your notifications",
         )
         .on_click({
             let client = self.client.clone();
-            |_, cx| {
-                cx.spawn(|cx| async move {
+            move |_, cx| {
+                let client = client.clone();
+                cx.spawn(move |cx| async move {
                     client.authenticate_and_connect(true, &cx).log_err().await;
                 })
                 .detach()
@@ -477,7 +486,7 @@ impl NotificationPanel {
         self.current_notification_toast = Some((
             notification_id,
             cx.spawn(|this, mut cx| async move {
-                cx.background().timer(TOAST_DURATION).await;
+                cx.background_executor().timer(TOAST_DURATION).await;
                 this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
                     .ok();
             }),
@@ -487,8 +496,8 @@ impl NotificationPanel {
             .update(cx, |workspace, cx| {
                 workspace.dismiss_notification::<NotificationToast>(0, cx);
                 workspace.show_notification(0, cx, |cx| {
-                    let workspace = cx.weak_handle();
-                    cx.add_view(|_| NotificationToast {
+                    let workspace = cx.view().downgrade();
+                    cx.build_view(|_| NotificationToast {
                         notification_id,
                         actor,
                         text,
@@ -527,9 +536,9 @@ impl NotificationPanel {
 impl Render for NotificationPanel {
     type Element = AnyElement;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement {
+    fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement {
         if self.client.user_id().is_none() {
-            self.render_sign_in_prompt(cx)
+            self.render_sign_in_prompt()
         } else if self.notification_list.item_count() == 0 {
             self.render_empty_state()
         } else {
@@ -569,7 +578,7 @@ impl Render for NotificationPanel {
 
 impl FocusableView for NotificationPanel {
     fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle
+        self.focus_handle.clone()
     }
 }
 
@@ -647,10 +656,10 @@ pub enum ToastEvent {
 }
 
 impl NotificationToast {
-    fn focus_notification_panel(&self, cx: &mut AppContext) {
+    fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
         let workspace = self.workspace.clone();
         let notification_id = self.notification_id;
-        cx.defer(move |cx| {
+        cx.defer(move |_, cx| {
             workspace
                 .update(cx, |workspace, cx| {
                     if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
@@ -679,19 +688,17 @@ impl Render for NotificationToast {
             .child(Label::new(self.text.clone()))
             .child(
                 IconButton::new("close", Icon::Close)
-                    .on_click(|_, cx| cx.emit(ToastEvent::Dismiss)),
+                    .on_click(cx.listener(|_, _, cx| cx.emit(ToastEvent::Dismiss))),
             )
-            .on_click({
-                let this = cx.view().clone();
-                |_, cx| {
-                    this.update(cx, |this, cx| this.focus_notification_panel(cx));
-                    cx.emit(ToastEvent::Dismiss);
-                }
-            })
+            .on_click(cx.listener(|this, _, cx| {
+                this.focus_notification_panel(cx);
+                cx.emit(ToastEvent::Dismiss);
+            }))
     }
 }
 
 impl EventEmitter<ToastEvent> for NotificationToast {}
+impl EventEmitter<DismissEvent> for NotificationToast {}
 
 fn format_timestamp(
     mut timestamp: OffsetDateTime,

crates/zed2/Cargo.toml 🔗

@@ -49,6 +49,7 @@ lsp = { package = "lsp2", path = "../lsp2" }
 menu = { package = "menu2", path = "../menu2" }
 # language_tools = { path = "../language_tools" }
 node_runtime = { path = "../node_runtime" }
+notifications = { package = "notifications2", path = "../notifications2" }
 assistant = { package = "assistant2", path = "../assistant2" }
 outline = { package = "outline2", path = "../outline2" }
 # plugin_runtime = { path = "../plugin_runtime",optional = true }

crates/zed2/src/main.rs 🔗

@@ -220,6 +220,7 @@ fn main() {
         // activity_indicator::init(cx);
         // language_tools::init(cx);
         call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
+        notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
         collab_ui::init(&app_state, cx);
         feedback::init(cx);
         welcome::init(cx);

crates/zed2/src/zed2.rs 🔗

@@ -175,14 +175,14 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                 assistant_panel,
                 channels_panel,
                 chat_panel,
-                //     notification_panel,
+                notification_panel,
             ) = futures::try_join!(
                 project_panel,
                 terminal_panel,
                 assistant_panel,
                 channels_panel,
                 chat_panel,
-                //     notification_panel,
+                notification_panel,
             )?;
 
             workspace_handle.update(&mut cx, |workspace, cx| {
@@ -192,7 +192,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
                 workspace.add_panel(assistant_panel, cx);
                 workspace.add_panel(channels_panel, cx);
                 workspace.add_panel(chat_panel, cx);
-                //     workspace.add_panel(notification_panel, cx);
+                workspace.add_panel(notification_panel, cx);
 
                 // if !was_deserialized
                 //     && workspace