Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
patch: Add stats to footer
Merged did:key:z6MkgFq6...nBGz opened 2 years ago
5 files changed +121 -59 67b38687 42d0ce70
modified bin/commands/inbox/flux/select/ui.rs
@@ -308,10 +308,10 @@ impl Render<()> for Notifications {
            layout[2],
            FooterProps {
                cells: [
-
                    span::badge("/".to_string()),
+
                    span::badge("/".to_string()).into(),
                    String::from("").into(),
                    String::from("").into(),
-
                    progress.clone(),
+
                    progress.clone().into(),
                ],
                widths: [
                    Constraint::Length(3),
modified bin/commands/issue/flux/select/ui.rs
@@ -337,10 +337,13 @@ impl Render<()> for Issues {
            layout[2],
            FooterProps {
                cells: [
-
                    span::badge("/".to_string()),
-
                    span::default(self.props.filter.to_string()).magenta().dim(),
+
                    span::badge("/".to_string()).into(),
+
                    span::default(self.props.filter.to_string())
+
                        .magenta()
+
                        .dim()
+
                        .into(),
                    String::from("").into(),
-
                    progress.clone(),
+
                    progress.clone().into(),
                ],
                widths: [
                    Constraint::Length(3),
modified bin/commands/patch/flux/select/ui.rs
@@ -1,13 +1,17 @@
-
use std::cmp;
+
use std::collections::HashMap;
use std::vec;

-
use ratatui::style::Stylize;
+
use radicle::patch;
+

use tokio::sync::mpsc::UnboundedSender;

use termion::event::Key;

use ratatui::backend::Backend;
+
use ratatui::layout::Alignment;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
+
use ratatui::style::Stylize;
+
use ratatui::text::Line;

use radicle_tui as tui;

@@ -102,45 +106,49 @@ impl Widget<PatchesState, Action> for ListPage {
            }
            Key::Char('c') => {
                if let Some(selected) = &self.props.selected {
+
                    let selection = Selection {
+
                        operation: Some(PatchOperation::Checkout.to_string()),
+
                        ids: vec![selected.id],
+
                        args: vec![],
+
                    };
                    let _ = self.action_tx.send(Action::Exit {
-
                        selection: Some(Selection {
-
                            operation: Some(PatchOperation::Checkout.to_string()),
-
                            ids: vec![selected.id],
-
                            args: vec![],
-
                        }),
+
                        selection: Some(selection),
                    });
                }
            }
            Key::Char('m') => {
                if let Some(selected) = &self.props.selected {
+
                    let selection = Selection {
+
                        operation: Some(PatchOperation::Comment.to_string()),
+
                        ids: vec![selected.id],
+
                        args: vec![],
+
                    };
                    let _ = self.action_tx.send(Action::Exit {
-
                        selection: Some(Selection {
-
                            operation: Some(PatchOperation::Comment.to_string()),
-
                            ids: vec![selected.id],
-
                            args: vec![],
-
                        }),
+
                        selection: Some(selection),
                    });
                }
            }
            Key::Char('e') => {
                if let Some(selected) = &self.props.selected {
+
                    let selection = Selection {
+
                        operation: Some(PatchOperation::Edit.to_string()),
+
                        ids: vec![selected.id],
+
                        args: vec![],
+
                    };
                    let _ = self.action_tx.send(Action::Exit {
-
                        selection: Some(Selection {
-
                            operation: Some(PatchOperation::Edit.to_string()),
-
                            ids: vec![selected.id],
-
                            args: vec![],
-
                        }),
+
                        selection: Some(selection),
                    });
                }
            }
            Key::Char('d') => {
                if let Some(selected) = &self.props.selected {
+
                    let selection = Selection {
+
                        operation: Some(PatchOperation::Delete.to_string()),
+
                        ids: vec![selected.id],
+
                        args: vec![],
+
                    };
                    let _ = self.action_tx.send(Action::Exit {
-
                        selection: Some(Selection {
-
                            operation: Some(PatchOperation::Delete.to_string()),
-
                            ids: vec![selected.id],
-
                            args: vec![],
-
                        }),
+
                        selection: Some(selection),
                    });
                }
            }
@@ -183,13 +191,39 @@ impl Render<()> for ListPage {
struct PatchesProps {
    patches: Vec<PatchItem>,
    filter: Filter,
+
    stats: HashMap<String, usize>,
}

impl From<&PatchesState> for PatchesProps {
    fn from(state: &PatchesState) -> Self {
+
        let mut draft = 0;
+
        let mut open = 0;
+
        let mut archived = 0;
+
        let mut merged = 0;
+

+
        for patch in &state.patches {
+
            match patch.state {
+
                patch::State::Draft => draft += 1,
+
                patch::State::Open { conflicts: _ } => open += 1,
+
                patch::State::Archived => archived += 1,
+
                patch::State::Merged {
+
                    commit: _,
+
                    revision: _,
+
                } => merged += 1,
+
            }
+
        }
+

+
        let stats = HashMap::from([
+
            ("Draft".to_string(), draft),
+
            ("Open".to_string(), open),
+
            ("Archived".to_string(), archived),
+
            ("Merged".to_string(), merged),
+
        ]);
+

        Self {
            patches: state.patches.clone(),
            filter: state.filter.clone(),
+
            stats,
        }
    }
}
@@ -299,17 +333,6 @@ impl Render<()> for Patches {
            Constraint::Length(16),
        ];

-
        let progress = {
-
            let step = self
-
                .table
-
                .selected()
-
                .map(|selected| selected.saturating_add(1).to_string())
-
                .unwrap_or("-".to_string());
-
            let length = self.props.patches.len().to_string();
-

-
            span::badge(format!("{}/{}", cmp::min(&step, &length), length))
-
        };
-

        self.header.render::<B>(
            frame,
            layout[0],
@@ -346,15 +369,41 @@ impl Render<()> for Patches {
            },
        );

+
        let (step, len) = self.table.progress(self.props.patches.len());
+

+
        let progress = span::badge(format!("{}/{}", step, len));
+
        let filter = span::default(self.props.filter.to_string()).magenta().dim();
+
        let stats = Line::from(
+
            [
+
                span::default(self.props.stats.get("Draft").unwrap_or(&0).to_string()).dim(),
+
                span::default(" Draft".to_string()).dim(),
+
                span::default(" | ".to_string()).dim(),
+
                span::positive(self.props.stats.get("Open").unwrap_or(&0).to_string()).dim(),
+
                span::default(" Open".to_string()).dim(),
+
                span::default(" | ".to_string()).dim(),
+
                span::default(self.props.stats.get("Merged").unwrap_or(&0).to_string())
+
                    .magenta()
+
                    .dim(),
+
                span::default(" Merged".to_string()).dim(),
+
                span::default(" | ".to_string()).dim(),
+
                span::default(self.props.stats.get("Archived").unwrap_or(&0).to_string())
+
                    .yellow()
+
                    .dim(),
+
                span::default(" Archived".to_string()).dim(),
+
            ]
+
            .to_vec(),
+
        )
+
        .alignment(Alignment::Right);
+

        self.footer.render::<B>(
            frame,
            layout[2],
            FooterProps {
                cells: [
-
                    span::badge("/".to_string()),
-
                    span::default(self.props.filter.to_string()).magenta().dim(),
-
                    String::from("").into(),
-
                    progress.clone(),
+
                    span::badge("/".to_string()).into(),
+
                    filter.into(),
+
                    stats.into(),
+
                    progress.clone().into(),
                ],
                widths: [
                    Constraint::Length(3),
modified src/flux/ui/span.rs
@@ -1,57 +1,57 @@
use ratatui::style::{Style, Stylize};
-
use ratatui::text::Text;
+
use ratatui::text::Span;

use crate::flux::ui::theme::style;

-
pub fn blank() -> Text<'static> {
-
    Text::styled("", Style::default())
+
pub fn blank() -> Span<'static> {
+
    Span::styled("", Style::default())
}

-
pub fn default(content: String) -> Text<'static> {
-
    Text::styled(content, Style::default())
+
pub fn default(content: String) -> Span<'static> {
+
    Span::styled(content, Style::default())
}

-
pub fn primary(content: String) -> Text<'static> {
+
pub fn primary(content: String) -> Span<'static> {
    default(content).style(style::cyan())
}

-
pub fn secondary(content: String) -> Text<'static> {
+
pub fn secondary(content: String) -> Span<'static> {
    default(content).style(style::magenta())
}

-
pub fn ternary(content: String) -> Text<'static> {
+
pub fn ternary(content: String) -> Span<'static> {
    default(content).style(style::blue())
}

-
pub fn positive(content: String) -> Text<'static> {
+
pub fn positive(content: String) -> Span<'static> {
    default(content).style(style::green())
}

-
pub fn negative(content: String) -> Text<'static> {
+
pub fn negative(content: String) -> Span<'static> {
    default(content).style(style::red())
}

-
pub fn badge(content: String) -> Text<'static> {
+
pub fn badge(content: String) -> Span<'static> {
    let content = &format!(" {content} ");
    default(content.to_string()).magenta().reversed()
}

-
pub fn alias(content: String) -> Text<'static> {
+
pub fn alias(content: String) -> Span<'static> {
    secondary(content)
}

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

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

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

-
pub fn notification_type(content: String) -> Text<'static> {
+
pub fn notification_type(content: String) -> Span<'static> {
    default(content).style(style::gray().dim())
}
modified src/flux/ui/widget.rs
@@ -1,5 +1,6 @@
pub mod container;

+
use std::cmp;
use std::fmt::Debug;

use tokio::sync::mpsc::UnboundedSender;
@@ -159,6 +160,15 @@ impl<A> Table<A> {
        });
        self.state.select(selected);
    }
+

+
    pub fn progress(&self, len: usize) -> (usize, usize) {
+
        let step = self
+
            .selected()
+
            .map(|selected| selected.saturating_add(1))
+
            .unwrap_or_default();
+

+
        (cmp::min(step, len), len)
+
    }
}

impl<S, A> Widget<S, A> for Table<A> {
@@ -234,7 +244,7 @@ where
            frame.render_widget(block, area);

            let center = layout::centered_rect(area, 50, 10);
-
            let hint = span::default("Nothing to show".to_string())
+
            let hint = Text::from(span::default("Nothing to show".to_string()))
                .centered()
                .light_magenta()
                .dim();