diff --git a/Cargo.lock b/Cargo.lock index 849eaab25f..73123915d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12046,6 +12046,7 @@ dependencies = [ "lsp2", "menu2", "node_runtime", + "notifications2", "num_cpus", "outline2", "parking_lot 0.11.2", diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 8c2d112f09..2b931f7085 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -422,8 +422,8 @@ impl CollabTitlebarItem { current_user: &Arc, ) -> Option { 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) } diff --git a/crates/collab_ui2/src/face_pile.rs b/crates/collab_ui2/src/face_pile.rs index 9b2406024f..d181509c46 100644 --- a/crates/collab_ui2/src/face_pile.rs +++ b/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; diff --git a/crates/collab_ui2/src/notification_panel.rs b/crates/collab_ui2/src/notification_panel.rs index 75c7cb3404..8053337078 100644 --- a/crates/collab_ui2/src/notification_panel.rs +++ b/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(¬ification, 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::(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) -> bool { if let Notification::ChannelMessageMention { channel_id, .. } = ¬ification { if let Some(workspace) = self.workspace.upgrade() { - return workspace - .read_with(cx, |workspace, cx| { - if let Some(panel) = workspace.panel::(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::(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) -> 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::(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) -> AnyElement { + fn render(&mut self, _: &mut ViewContext) -> 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) { 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::(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 for NotificationToast {} +impl EventEmitter for NotificationToast {} fn format_timestamp( mut timestamp: OffsetDateTime, diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index ed7ed180f5..4dad72c302 100644 --- a/crates/zed2/Cargo.toml +++ b/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 } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 92b20bf271..22be566bf9 100644 --- a/crates/zed2/src/main.rs +++ b/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); diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 1414bbaa13..65331a05a7 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -175,14 +175,14 @@ pub fn initialize_workspace(app_state: Arc, 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, 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