From be3cc6458c27d341e284af183239db4075f49262 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 1 Nov 2023 16:34:42 -0400 Subject: [PATCH] Implement Notifications Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com> --- .../ui2/src/components/notifications_panel.rs | 153 +++++++----------- crates/ui2/src/elements/icon.rs | 2 + crates/ui2/src/prelude.rs | 18 +++ crates/ui2/src/static_data.rs | 37 ++++- 4 files changed, 110 insertions(+), 100 deletions(-) diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 367e0d0ba677815ebd2bac575caa2d160278ec0e..6bcc9574c9589f0bec201dd94c9941978a831861 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -1,6 +1,6 @@ use crate::{ h_stack, prelude::*, static_new_notification_items, v_stack, Avatar, Button, Icon, IconButton, - IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, Stack, + IconElement, Label, LabelColor, LineHeightStyle, ListHeaderMeta, ListSeparator, UnreadIndicator, }; use crate::{ClickHandler, ListHeader}; @@ -67,6 +67,18 @@ pub enum ButtonOrIconButton { IconButton(IconButton), } +impl From> for ButtonOrIconButton { + fn from(value: Button) -> Self { + Self::Button(value) + } +} + +impl From> for ButtonOrIconButton { + fn from(value: IconButton) -> Self { + Self::IconButton(value) + } +} + pub struct NotificationAction { button: ButtonOrIconButton, tooltip: SharedString, @@ -80,19 +92,25 @@ pub struct NotificationAction { taken_message: (Option, SharedString), } +impl NotificationAction { + pub fn new( + button: impl Into>, + tooltip: impl Into, + (icon, taken_message): (Option, impl Into), + ) -> Self { + Self { + button: button.into(), + tooltip: tooltip.into(), + taken_message: (icon, taken_message.into()), + } + } +} + pub struct NotificationWithActions { notification: Notification, actions: [NotificationAction; 2], } -/// Represents a person with a Zed account's public profile. -/// All data in this struct should be considered public. -pub struct PublicActor { - username: SharedString, - avatar: SharedString, - is_contact: bool, -} - pub enum ActorOrIcon { Actor(PublicActor), Icon(Icon), @@ -162,13 +180,13 @@ impl Notification { /// Requires a click action. pub fn new_actor_message( id: impl Into, - message: SharedString, + message: impl Into, actor: PublicActor, click_action: ClickHandler, ) -> Self { Self::new( id.into(), - message, + message.into(), ActorOrIcon::Actor(actor), Some(click_action), ) @@ -179,13 +197,13 @@ impl Notification { /// Requires a click action. pub fn new_icon_message( id: impl Into, - message: SharedString, + message: impl Into, icon: Icon, click_action: ClickHandler, ) -> Self { Self::new( id.into(), - message, + message.into(), ActorOrIcon::Icon(icon), Some(click_action), ) @@ -197,12 +215,11 @@ impl Notification { /// Cannot take a click action due to required actions. pub fn new_actor_with_actions( id: impl Into, - message: SharedString, + message: impl Into, actor: PublicActor, - click_action: ClickHandler, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message, ActorOrIcon::Actor(actor), None).actions(actions) + Self::new(id.into(), message.into(), ActorOrIcon::Actor(actor), None).actions(actions) } /// Creates a new notification with an icon slot @@ -211,12 +228,11 @@ impl Notification { /// Cannot take a click action due to required actions. pub fn new_icon_with_actions( id: impl Into, - message: SharedString, + message: impl Into, icon: Icon, - click_action: ClickHandler, actions: [NotificationAction; 2], ) -> Self { - Self::new(id.into(), message, ActorOrIcon::Icon(icon), None).actions(actions) + Self::new(id.into(), message.into(), ActorOrIcon::Icon(icon), None).actions(actions) } fn on_click(mut self, handler: ClickHandler) -> Self { @@ -253,45 +269,6 @@ impl Notification { } } - fn render_actions(&self, cx: &mut ViewContext) -> impl Component { - // match (&self.actions, &self.action_taken) { - // // Show nothing - // (None, _) => div(), - // // Show the taken_message - // (Some(_), Some(action_taken)) => h_stack() - // .children( - // action_taken - // .taken_message - // .0 - // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - // ) - // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // // Show the actions - // (Some(actions), None) => h_stack() - // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), - // })) - // .collect::>(), - } - - // if let Some(actions) = &self.actions { - // let action_children = actions - // .iter() - // .map(|action| match &action.button { - // ButtonOrIconButton::Button(button) => { - // div().class("action_button").child(button.label.clone()) - // } - // ButtonOrIconButton::IconButton(icon_button) => div() - // .class("action_icon_button") - // .child(icon_button.icon.to_string()), - // }) - // .collect::>(); - - // el = el.child(h_stack().children(action_children)); - // } else { - // el = el.child(h_stack().child(div())); - // } - } - fn render_slot(&self, cx: &mut ViewContext) -> impl Component { match &self.slot { ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), @@ -336,44 +313,30 @@ impl Notification { ) .child(self.render_meta_items(cx)), ) - .child( - - match (self.actions, self.action_taken) { - // Show nothing - (None, _) => div(), - // Show the taken_message - (Some(_), Some(action_taken)) => h_stack() - .children( - action_taken - .taken_message - .0 - .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - ) - .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // Show the actions - (Some(actions), None) => h_stack() - + .child(match (self.actions, self.action_taken) { + // Show nothing + (None, _) => div(), + // Show the taken_message + (Some(_), Some(action_taken)) => h_stack() + .children(action_taken.taken_message.0.map(|icon| { + IconElement::new(icon).color(crate::IconColor::Muted) + })) + .child( + Label::new(action_taken.taken_message.1.clone()) + .color(LabelColor::Muted), + ), + // Show the actions + (Some(actions), None) => { + h_stack().children(actions.map(|action| match action.button { + ButtonOrIconButton::Button(button) => { + Component::render(button) + } + ButtonOrIconButton::IconButton(icon_button) => { + Component::render(icon_button) + } + })) } - - // match (&self.actions, &self.action_taken) { - // // Show nothing - // (None, _) => div(), - // // Show the taken_message - // (Some(_), Some(action_taken)) => h_stack() - // .children( - // action_taken - // .taken_message - // .0 - // .map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), - // ) - // .child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), - // // Show the actions - // (Some(actions), None) => h_stack() - // .children(actions.iter().map(actiona.ction.tton Component::render(button),Component::render(icon_button))), - // })) - // .collect::>(), - - ), + }), ), ) } diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index eef80cb5ada085c892cbe039dae8b3922ba58215..2038e5a0005d4d44c7842beeb855a84ec7e438d0 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -49,6 +49,7 @@ pub enum Icon { AudioOff, AudioOn, Bolt, + Check, ChevronDown, ChevronLeft, ChevronRight, @@ -105,6 +106,7 @@ impl Icon { Icon::AudioOff => "icons/speaker-off.svg", Icon::AudioOn => "icons/speaker-loud.svg", Icon::Bolt => "icons/bolt.svg", + Icon::Check => "icons/check.svg", Icon::ChevronDown => "icons/chevron_down.svg", Icon::ChevronLeft => "icons/chevron_left.svg", Icon::ChevronRight => "icons/chevron_right.svg", diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index b424ce61235713d53ca6b37c25bdd663ae514f77..53adc8c0f9ee5eb174f020eea44aa5fdd8489d53 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -19,6 +19,24 @@ pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems { rems(*settings.ui_scale * UI_SCALE_RATIO * size) } +/// Represents a person with a Zed account's public profile. +/// All data in this struct should be considered public. +pub struct PublicActor { + pub username: SharedString, + pub avatar: SharedString, + pub is_contact: bool, +} + +impl PublicActor { + pub fn new(username: impl Into, avatar: impl Into) -> Self { + Self { + username: username.into(), + avatar: avatar.into(), + is_contact: false, + } + } +} + #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum FileSystemStatus { #[default] diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index dc724ec5c45c4ce203426261cc74875a08220010..7e206f0cf6ecf7e15e2fdf919423df89b1c9d452 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; use std::str::FromStr; +use std::sync::Arc; use gpui2::{AppContext, ViewContext}; use rand::Rng; @@ -7,12 +8,13 @@ use theme2::ActiveTheme; use crate::{ Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, - HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, - ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, NotificationItem, PaletteItem, - Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, - VideoStatus, + HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader, + Livestream, MicStatus, ModifierKeys, Notification, NotificationItem, PaletteItem, Player, + PlayerCallStatus, PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab, + ToggleState, VideoStatus, }; use crate::{HighlightedText, ListDetailsEntry}; +use crate::{ListItem, NotificationAction}; pub fn static_tabs_example() -> Vec { vec![ @@ -327,8 +329,33 @@ pub fn static_players_with_call_status() -> Vec { } pub fn static_new_notification_items_2() -> Vec> { - vec![] + vec![ + NotificationItem::Message(Notification::new_icon_message( + "notif-1", + "You were mentioned in a note.", + Icon::AtSign, + Arc::new(|_, _| {}), + )), + NotificationItem::Message(Notification::new_actor_with_actions( + "notif-2", + "as-cii sent you a contact request.", + PublicActor::new("as-cii", "http://github.com/as-cii.png?s=50"), + [ + NotificationAction::new( + Button::new("Decline"), + "Decline Request", + (Some(Icon::XCircle), "Declined"), + ), + NotificationAction::new( + Button::new("Accept").variant(crate::ButtonVariant::Filled), + "Accept Request", + (Some(Icon::Check), "Accepted"), + ), + ], + )), + ] } + pub fn static_new_notification_items() -> Vec> { vec![ ListItem::Header(ListSubHeader::new("New")),