Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Introduce issue item
Erik Kundt committed 2 years ago
commit c50373514789f3d97fba510a8b9c112280d1d602
parent 0c5a83353698ca60830f9590c194aa3df0249d6e
2 files changed +151 -4
modified src/flux/ui/cob.rs
@@ -1,11 +1,12 @@
+
use radicle::crypto::PublicKey;
use radicle::identity::Did;
use radicle::node::{Alias, NodeId};
-
use radicle::Profile;
-
use ratatui::style::Stylize;
+
use radicle::{issue, Profile};
+
use ratatui::style::{Color, Style, Stylize};
use ratatui::widgets::Cell;

-
use radicle::cob::{self, ObjectId, Timestamp};
-
use radicle::issue::Issues;
+
use radicle::cob::{self, Label, ObjectId, Timestamp};
+
use radicle::issue::{Issue, IssueId, Issues};
use radicle::node::notifications::{Notification, NotificationId, NotificationKind};
use radicle::node::AliasStore;
use radicle::patch::Patches;
@@ -224,3 +225,145 @@ impl ToRow<8> for NotificationItem {
        ]
    }
}
+

+
#[derive(Clone, Debug)]
+
pub struct IssueItem {
+
    /// Issue OID.
+
    pub id: IssueId,
+
    /// Issue state.
+
    pub state: issue::State,
+
    /// Issue title.
+
    pub title: String,
+
    /// Issue author.
+
    pub author: AuthorItem,
+
    /// Issue labels.
+
    pub labels: Vec<Label>,
+
    /// Issue assignees.
+
    pub assignees: Vec<AuthorItem>,
+
    /// Time when issue was opened.
+
    pub timestamp: Timestamp,
+
}
+

+
impl IssueItem {
+
    pub fn new(profile: &Profile, issue: (IssueId, Issue)) -> Result<Self, anyhow::Error> {
+
        let (id, issue) = issue;
+

+
        Ok(Self {
+
            id,
+
            state: *issue.state(),
+
            title: issue.title().into(),
+
            author: AuthorItem {
+
                nid: Some(*issue.author().id),
+
                alias: profile.aliases().alias(&issue.author().id),
+
                you: *issue.author().id == *profile.did(),
+
            },
+
            labels: issue.labels().cloned().collect(),
+
            assignees: issue
+
                .assignees()
+
                .map(|did| AuthorItem {
+
                    nid: Some(**did),
+
                    alias: profile.aliases().alias(did),
+
                    you: *did == profile.did(),
+
                })
+
                .collect::<Vec<_>>(),
+
            timestamp: issue.timestamp(),
+
        })
+
    }
+
}
+

+
impl ToRow<8> for IssueItem {
+
    fn to_row(&self) -> [Cell; 8] {
+
        let (state, state_color) = format_issue_state(&self.state);
+

+
        let state = span::default(state).style(Style::default().fg(state_color));
+
        let id = span::primary(format::cob(&self.id));
+
        let title = span::default(self.title.clone());
+

+
        let author = match &self.author.alias {
+
            Some(alias) => {
+
                if self.author.you {
+
                    span::alias(format!("{} (you)", alias))
+
                } else {
+
                    span::alias(alias.to_string())
+
                }
+
            }
+
            None => match self.author.nid {
+
                Some(nid) => span::alias(format::did(&Did::from(nid))).dim(),
+
                None => span::alias("".to_string()),
+
            },
+
        };
+
        let did = match self.author.nid {
+
            Some(nid) => span::alias(format::did(&Did::from(nid))).dim(),
+
            None => span::alias("".to_string()),
+
        };
+
        let labels = span::labels(format_labels(&self.labels));
+
        let assignees = self
+
            .assignees
+
            .iter()
+
            .map(|author| (author.nid, author.alias.clone(), author.you))
+
            .collect::<Vec<_>>();
+
        let assignees = span::alias(format_assignees(&assignees));
+
        let opened = span::timestamp(format::timestamp(&self.timestamp));
+

+
        [
+
            state.into(),
+
            id.into(),
+
            title.into(),
+
            author.into(),
+
            did.into(),
+
            labels.into(),
+
            assignees.into(),
+
            opened.into(),
+
        ]
+
    }
+
}
+

+
pub fn format_issue_state(state: &issue::State) -> (String, Color) {
+
    match state {
+
        issue::State::Open => (" ● ".into(), Color::Green),
+
        issue::State::Closed { reason: _ } => (" ● ".into(), Color::Red),
+
    }
+
}
+

+
pub fn format_labels(labels: &[Label]) -> String {
+
    let mut output = String::new();
+
    let mut labels = labels.iter().peekable();
+

+
    while let Some(label) = labels.next() {
+
        output.push_str(&label.to_string());
+

+
        if labels.peek().is_some() {
+
            output.push(',');
+
        }
+
    }
+
    output
+
}
+

+
pub fn format_author(did: &Did, alias: &Option<Alias>, is_you: bool) -> String {
+
    let author = match alias {
+
        Some(alias) => format!("{alias}"),
+
        None => format::did(did),
+
    };
+

+
    if is_you {
+
        format!("{} (you)", author)
+
    } else {
+
        author
+
    }
+
}
+

+
pub fn format_assignees(assignees: &[(Option<PublicKey>, Option<Alias>, bool)]) -> String {
+
    let mut output = String::new();
+
    let mut assignees = assignees.iter().peekable();
+

+
    while let Some((assignee, alias, is_you)) = assignees.next() {
+
        if let Some(assignee) = assignee {
+
            output.push_str(&format_author(&Did::from(assignee), alias, *is_you));
+
        }
+

+
        if assignees.peek().is_some() {
+
            output.push(',');
+
        }
+
    }
+
    output
+
}
modified src/flux/ui/span.rs
@@ -36,6 +36,10 @@ pub fn alias(content: String) -> Text<'static> {
    secondary(content)
}

+
pub fn labels(content: String) -> Text<'static> {
+
    ternary(content)
+
}
+

pub fn timestamp(content: String) -> Text<'static> {
    default(content).style(style::gray().dim())
}