Implement Notifications

Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
This commit is contained in:
Nate Butler 2023-11-01 16:34:42 -04:00
parent 3f74f75dee
commit be3cc6458c
4 changed files with 110 additions and 100 deletions

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
h_stack, prelude::*, static_new_notification_items, v_stack, Avatar, Button, Icon, IconButton, 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, UnreadIndicator,
}; };
use crate::{ClickHandler, ListHeader}; use crate::{ClickHandler, ListHeader};
@ -67,6 +67,18 @@ pub enum ButtonOrIconButton<V: 'static> {
IconButton(IconButton<V>), IconButton(IconButton<V>),
} }
impl<V: 'static> From<Button<V>> for ButtonOrIconButton<V> {
fn from(value: Button<V>) -> Self {
Self::Button(value)
}
}
impl<V: 'static> From<IconButton<V>> for ButtonOrIconButton<V> {
fn from(value: IconButton<V>) -> Self {
Self::IconButton(value)
}
}
pub struct NotificationAction<V: 'static> { pub struct NotificationAction<V: 'static> {
button: ButtonOrIconButton<V>, button: ButtonOrIconButton<V>,
tooltip: SharedString, tooltip: SharedString,
@ -80,19 +92,25 @@ pub struct NotificationAction<V: 'static> {
taken_message: (Option<Icon>, SharedString), taken_message: (Option<Icon>, SharedString),
} }
impl<V: 'static> NotificationAction<V> {
pub fn new(
button: impl Into<ButtonOrIconButton<V>>,
tooltip: impl Into<SharedString>,
(icon, taken_message): (Option<Icon>, impl Into<SharedString>),
) -> Self {
Self {
button: button.into(),
tooltip: tooltip.into(),
taken_message: (icon, taken_message.into()),
}
}
}
pub struct NotificationWithActions<V: 'static> { pub struct NotificationWithActions<V: 'static> {
notification: Notification<V>, notification: Notification<V>,
actions: [NotificationAction<V>; 2], actions: [NotificationAction<V>; 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 { pub enum ActorOrIcon {
Actor(PublicActor), Actor(PublicActor),
Icon(Icon), Icon(Icon),
@ -162,13 +180,13 @@ impl<V> Notification<V> {
/// Requires a click action. /// Requires a click action.
pub fn new_actor_message( pub fn new_actor_message(
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: SharedString, message: impl Into<SharedString>,
actor: PublicActor, actor: PublicActor,
click_action: ClickHandler<V>, click_action: ClickHandler<V>,
) -> Self { ) -> Self {
Self::new( Self::new(
id.into(), id.into(),
message, message.into(),
ActorOrIcon::Actor(actor), ActorOrIcon::Actor(actor),
Some(click_action), Some(click_action),
) )
@ -179,13 +197,13 @@ impl<V> Notification<V> {
/// Requires a click action. /// Requires a click action.
pub fn new_icon_message( pub fn new_icon_message(
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: SharedString, message: impl Into<SharedString>,
icon: Icon, icon: Icon,
click_action: ClickHandler<V>, click_action: ClickHandler<V>,
) -> Self { ) -> Self {
Self::new( Self::new(
id.into(), id.into(),
message, message.into(),
ActorOrIcon::Icon(icon), ActorOrIcon::Icon(icon),
Some(click_action), Some(click_action),
) )
@ -197,12 +215,11 @@ impl<V> Notification<V> {
/// Cannot take a click action due to required actions. /// Cannot take a click action due to required actions.
pub fn new_actor_with_actions( pub fn new_actor_with_actions(
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: SharedString, message: impl Into<SharedString>,
actor: PublicActor, actor: PublicActor,
click_action: ClickHandler<V>,
actions: [NotificationAction<V>; 2], actions: [NotificationAction<V>; 2],
) -> Self { ) -> 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 /// Creates a new notification with an icon slot
@ -211,12 +228,11 @@ impl<V> Notification<V> {
/// Cannot take a click action due to required actions. /// Cannot take a click action due to required actions.
pub fn new_icon_with_actions( pub fn new_icon_with_actions(
id: impl Into<ElementId>, id: impl Into<ElementId>,
message: SharedString, message: impl Into<SharedString>,
icon: Icon, icon: Icon,
click_action: ClickHandler<V>,
actions: [NotificationAction<V>; 2], actions: [NotificationAction<V>; 2],
) -> Self { ) -> 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<V>) -> Self { fn on_click(mut self, handler: ClickHandler<V>) -> Self {
@ -253,45 +269,6 @@ impl<V> Notification<V> {
} }
} }
fn render_actions(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
// 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::<Vec<_>>(),
}
// 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::<Vec<_>>();
// el = el.child(h_stack().children(action_children));
// } else {
// el = el.child(h_stack().child(div()));
// }
}
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> { fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
match &self.slot { match &self.slot {
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
@ -336,44 +313,30 @@ impl<V> Notification<V> {
) )
.child(self.render_meta_items(cx)), .child(self.render_meta_items(cx)),
) )
.child( .child(match (self.actions, self.action_taken) {
// Show nothing
match (self.actions, self.action_taken) { (None, _) => div(),
// Show nothing // Show the taken_message
(None, _) => div(), (Some(_), Some(action_taken)) => h_stack()
// Show the taken_message .children(action_taken.taken_message.0.map(|icon| {
(Some(_), Some(action_taken)) => h_stack() IconElement::new(icon).color(crate::IconColor::Muted)
.children( }))
action_taken .child(
.taken_message Label::new(action_taken.taken_message.1.clone())
.0 .color(LabelColor::Muted),
.map(|icon| IconElement::new(icon).color(crate::IconColor::Muted)), ),
) // Show the actions
.child(Label::new(action_taken.taken_message.1.clone()).color(LabelColor::Muted)), (Some(actions), None) => {
// Show the actions h_stack().children(actions.map(|action| match action.button {
(Some(actions), None) => h_stack() 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::<Vec<_>>(),
),
), ),
) )
} }

View file

@ -49,6 +49,7 @@ pub enum Icon {
AudioOff, AudioOff,
AudioOn, AudioOn,
Bolt, Bolt,
Check,
ChevronDown, ChevronDown,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
@ -105,6 +106,7 @@ impl Icon {
Icon::AudioOff => "icons/speaker-off.svg", Icon::AudioOff => "icons/speaker-off.svg",
Icon::AudioOn => "icons/speaker-loud.svg", Icon::AudioOn => "icons/speaker-loud.svg",
Icon::Bolt => "icons/bolt.svg", Icon::Bolt => "icons/bolt.svg",
Icon::Check => "icons/check.svg",
Icon::ChevronDown => "icons/chevron_down.svg", Icon::ChevronDown => "icons/chevron_down.svg",
Icon::ChevronLeft => "icons/chevron_left.svg", Icon::ChevronLeft => "icons/chevron_left.svg",
Icon::ChevronRight => "icons/chevron_right.svg", Icon::ChevronRight => "icons/chevron_right.svg",

View file

@ -19,6 +19,24 @@ pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems {
rems(*settings.ui_scale * UI_SCALE_RATIO * size) 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<SharedString>, avatar: impl Into<SharedString>) -> Self {
Self {
username: username.into(),
avatar: avatar.into(),
is_contact: false,
}
}
}
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
pub enum FileSystemStatus { pub enum FileSystemStatus {
#[default] #[default]

View file

@ -1,5 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc;
use gpui2::{AppContext, ViewContext}; use gpui2::{AppContext, ViewContext};
use rand::Rng; use rand::Rng;
@ -7,12 +8,13 @@ use theme2::ActiveTheme;
use crate::{ use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListHeaderMeta, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListSubHeader,
ListItem, ListSubHeader, Livestream, MicStatus, ModifierKeys, NotificationItem, PaletteItem, Livestream, MicStatus, ModifierKeys, Notification, NotificationItem, PaletteItem, Player,
Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, PlayerCallStatus, PlayerWithCallStatus, PublicActor, ScreenShareStatus, Symbol, Tab,
VideoStatus, ToggleState, VideoStatus,
}; };
use crate::{HighlightedText, ListDetailsEntry}; use crate::{HighlightedText, ListDetailsEntry};
use crate::{ListItem, NotificationAction};
pub fn static_tabs_example() -> Vec<Tab> { pub fn static_tabs_example() -> Vec<Tab> {
vec![ vec![
@ -327,8 +329,33 @@ pub fn static_players_with_call_status() -> Vec<PlayerWithCallStatus> {
} }
pub fn static_new_notification_items_2<V: 'static>() -> Vec<NotificationItem<V>> { pub fn static_new_notification_items_2<V: 'static>() -> Vec<NotificationItem<V>> {
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<V: 'static>() -> Vec<ListItem<V>> { pub fn static_new_notification_items<V: 'static>() -> Vec<ListItem<V>> {
vec![ vec![
ListItem::Header(ListSubHeader::new("New")), ListItem::Header(ListSubHeader::new("New")),