lib: Support notifications in flux apps

This commit is contained in:
Erik Kundt
2024-02-19 23:14:33 +01:00
parent eaa4d07c7b
commit cde549e30a
2 changed files with 166 additions and 5 deletions
+160
View File
@@ -0,0 +1,160 @@
use anyhow::anyhow;
use ratatui::widgets::Cell;
use radicle::cob::{self, ObjectId, Timestamp};
use radicle::issue::Issues;
use radicle::node::notifications::{Notification, NotificationId, NotificationKind};
use radicle::patch::Patches;
use radicle::storage::git::Repository;
use radicle::storage::{ReadRepository, RefUpdate};
use super::widget::ToRow;
use super::{format, span};
#[derive(Clone, Debug)]
pub enum NotificationKindItem {
Branch {
name: String,
summary: String,
status: String,
id: Option<ObjectId>,
},
Cob {
type_name: String,
summary: String,
status: String,
id: Option<ObjectId>,
},
}
impl TryFrom<(&Repository, &Notification)> for NotificationKindItem {
type Error = anyhow::Error;
fn try_from(value: (&Repository, &Notification)) -> Result<Self, Self::Error> {
let (repo, notification) = value;
// TODO: move out of here
let issues = Issues::open(repo)?;
let patches = Patches::open(repo)?;
match &notification.kind {
NotificationKind::Branch { name } => {
let (head, message) = if let Some(head) = notification.update.new() {
let message = repo.commit(head)?.summary().unwrap_or_default().to_owned();
(Some(head), message)
} else {
(None, String::new())
};
let status = match notification.update {
RefUpdate::Updated { .. } => "updated",
RefUpdate::Created { .. } => "created",
RefUpdate::Deleted { .. } => "deleted",
RefUpdate::Skipped { .. } => "skipped",
};
Ok(NotificationKindItem::Branch {
name: name.to_string(),
summary: message,
status: status.to_string(),
id: head.map(ObjectId::from),
})
}
NotificationKind::Cob { type_name, id } => {
let (category, summary) = if *type_name == *cob::issue::TYPENAME {
let issue = issues.get(id)?.ok_or(anyhow!("missing"))?;
(String::from("issue"), issue.title().to_owned())
} else if *type_name == *cob::patch::TYPENAME {
let patch = patches.get(id)?.ok_or(anyhow!("missing"))?;
(String::from("patch"), patch.title().to_owned())
} else {
(type_name.to_string(), "".to_owned())
};
let status = match notification.update {
RefUpdate::Updated { .. } => "updated",
RefUpdate::Created { .. } => "opened",
RefUpdate::Deleted { .. } => "deleted",
RefUpdate::Skipped { .. } => "skipped",
};
Ok(NotificationKindItem::Cob {
type_name: category.to_string(),
summary: summary.to_string(),
status: status.to_string(),
id: Some(*id),
})
}
}
}
}
#[derive(Clone, Debug)]
pub struct NotificationItem {
/// Unique notification ID.
pub id: NotificationId,
/// Mark this notification as seen.
pub seen: bool,
/// Wrapped notification kind.
pub kind: NotificationKindItem,
/// Time the update has happened.
timestamp: Timestamp,
}
impl TryFrom<(&Repository, &Notification)> for NotificationItem {
type Error = anyhow::Error;
fn try_from(value: (&Repository, &Notification)) -> Result<Self, Self::Error> {
let (repo, notification) = value;
let kind = NotificationKindItem::try_from((repo, notification))?;
Ok(NotificationItem {
id: notification.id,
seen: notification.status.is_read(),
kind,
timestamp: notification.timestamp.into(),
})
}
}
impl ToRow<7> for NotificationItem {
fn to_row(&self) -> [Cell; 7] {
let (type_name, summary, status, kind_id) = match &self.kind {
NotificationKindItem::Branch {
name,
summary,
status,
id: _,
} => ("branch".to_string(), summary, status, name.to_string()),
NotificationKindItem::Cob {
type_name,
summary,
status,
id,
} => {
let id = id.map(|id| format::cob(&id)).unwrap_or_default();
(type_name.to_string(), summary, status, id.to_string())
}
};
let id = span::default(format!(" {}", &self.id));
let seen = if self.seen {
span::blank()
} else {
span::positive(" ● ".into())
};
let type_name = span::secondary(type_name);
let summary = span::default(summary.to_string());
let kind_id = span::primary(kind_id);
let status = span::default(status.to_string());
let timestamp = span::timestamp(format::timestamp(&self.timestamp));
[
id.into(),
seen.into(),
type_name.into(),
summary.into(),
kind_id.into(),
status.into(),
timestamp.into(),
]
}
}
+6 -5
View File
@@ -3,16 +3,17 @@ use std::hash::Hash;
use std::time::Duration;
use anyhow::Result;
use radicle::node::notifications::NotificationId;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use radicle::cob::ObjectId;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use tuirealm::terminal::TerminalBridge;
use tuirealm::tui::layout::Rect;
use tuirealm::Frame;
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
use radicle::cob::ObjectId;
use radicle::node::notifications::NotificationId;
pub mod cob;
pub mod context;
pub mod log;
@@ -175,8 +176,8 @@ impl Window {
}
update = tui.update(&mut app)?;
resize = size != self.terminal.raw().size()?;
size = self.terminal.raw().size()?;
resize = size != self.terminal.raw_mut().size()?;
size = self.terminal.raw_mut().size()?;
}
Ok(tui.exit().unwrap().value)