mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-18 08:02:27 +00:00
660021f5e5
* Put the newest notifications at the top * Have at most 1 notification toast, which is non-interactive, but focuses the notification panel on click, and auto-dismisses on a timer.
105 lines
3.5 KiB
Rust
105 lines
3.5 KiB
Rust
use crate::proto;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::{map, Value};
|
|
use strum::{EnumVariantNames, VariantNames as _};
|
|
|
|
const KIND: &'static str = "kind";
|
|
const ENTITY_ID: &'static str = "entity_id";
|
|
|
|
/// A notification that can be stored, associated with a given recipient.
|
|
///
|
|
/// This struct is stored in the collab database as JSON, so it shouldn't be
|
|
/// changed in a backward-incompatible way. For example, when renaming a
|
|
/// variant, add a serde alias for the old name.
|
|
///
|
|
/// Most notification types have a special field which is aliased to
|
|
/// `entity_id`. This field is stored in its own database column, and can
|
|
/// be used to query the notification.
|
|
#[derive(Debug, Clone, PartialEq, Eq, EnumVariantNames, Serialize, Deserialize)]
|
|
#[serde(tag = "kind")]
|
|
pub enum Notification {
|
|
ContactRequest {
|
|
#[serde(rename = "entity_id")]
|
|
sender_id: u64,
|
|
},
|
|
ContactRequestAccepted {
|
|
#[serde(rename = "entity_id")]
|
|
responder_id: u64,
|
|
},
|
|
ChannelInvitation {
|
|
#[serde(rename = "entity_id")]
|
|
channel_id: u64,
|
|
channel_name: String,
|
|
inviter_id: u64,
|
|
},
|
|
ChannelMessageMention {
|
|
#[serde(rename = "entity_id")]
|
|
message_id: u64,
|
|
sender_id: u64,
|
|
channel_id: u64,
|
|
},
|
|
}
|
|
|
|
impl Notification {
|
|
pub fn to_proto(&self) -> proto::Notification {
|
|
let mut value = serde_json::to_value(self).unwrap();
|
|
let mut entity_id = None;
|
|
let value = value.as_object_mut().unwrap();
|
|
let Some(Value::String(kind)) = value.remove(KIND) else {
|
|
unreachable!("kind is the enum tag")
|
|
};
|
|
if let map::Entry::Occupied(e) = value.entry(ENTITY_ID) {
|
|
if e.get().is_u64() {
|
|
entity_id = e.remove().as_u64();
|
|
}
|
|
}
|
|
proto::Notification {
|
|
kind,
|
|
entity_id,
|
|
content: serde_json::to_string(&value).unwrap(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
pub fn from_proto(notification: &proto::Notification) -> Option<Self> {
|
|
let mut value = serde_json::from_str::<Value>(¬ification.content).ok()?;
|
|
let object = value.as_object_mut()?;
|
|
object.insert(KIND.into(), notification.kind.to_string().into());
|
|
if let Some(entity_id) = notification.entity_id {
|
|
object.insert(ENTITY_ID.into(), entity_id.into());
|
|
}
|
|
serde_json::from_value(value).ok()
|
|
}
|
|
|
|
pub fn all_variant_names() -> &'static [&'static str] {
|
|
Self::VARIANTS
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_notification() {
|
|
// Notifications can be serialized and deserialized.
|
|
for notification in [
|
|
Notification::ContactRequest { sender_id: 1 },
|
|
Notification::ContactRequestAccepted { responder_id: 2 },
|
|
Notification::ChannelInvitation {
|
|
channel_id: 100,
|
|
channel_name: "the-channel".into(),
|
|
inviter_id: 50,
|
|
},
|
|
Notification::ChannelMessageMention {
|
|
sender_id: 200,
|
|
channel_id: 30,
|
|
message_id: 1,
|
|
},
|
|
] {
|
|
let message = notification.to_proto();
|
|
let deserialized = Notification::from_proto(&message).unwrap();
|
|
assert_eq!(deserialized, notification);
|
|
}
|
|
|
|
// When notifications are serialized, the `kind` and `actor_id` fields are
|
|
// stored separately, and do not appear redundantly in the JSON.
|
|
let notification = Notification::ContactRequest { sender_id: 1 };
|
|
assert_eq!(notification.to_proto().content, "{}");
|
|
}
|