Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
inbox: Implement sorting
Erik Kundt committed 2 years ago
commit 78e18113112aac2b267a86aad4a315b25a9acebc
parent 38a7a54bd4dc10d61183d0edd9a0bfbff41cedc3
7 files changed +97 -15
modified bin/commands/inbox.rs
@@ -26,7 +26,13 @@ Usage
Other options

    --mode <MODE>           Set selection mode; see MODE below (default: operation)
+
    
+
    --sort-by <field>       Sort by `id` or `timestamp` (default: timestamp)
+
    --reverse, -r           Reverse the list
    --help                  Print help
+

+
    The MODE argument can be 'operation' or 'id'. 'operation' selects a notification id and
+
    an operation, whereas 'id' selects a notification id only.
"#,
};

@@ -47,6 +53,7 @@ pub enum OperationName {
pub struct SelectOptions {
    mode: select::Mode,
    filter: inbox::Filter,
+
    sort_by: inbox::SortBy,
}

impl Args for Options {
@@ -55,6 +62,8 @@ impl Args for Options {

        let mut parser = lexopt::Parser::from_args(args);
        let mut op: Option<OperationName> = None;
+
        let mut reverse = None;
+
        let mut field = None;
        let mut select_opts = SelectOptions::default();

        while let Some(arg) = parser.next()? {
@@ -75,6 +84,19 @@ impl Args for Options {
                    };
                }

+
                Long("reverse") | Short('r') => {
+
                    reverse = Some(true);
+
                }
+
                Long("sort-by") => {
+
                    let val = parser.value()?;
+

+
                    match terminal::args::string(&val).as_str() {
+
                        "timestamp" => field = Some("timestamp"),
+
                        "rowid" => field = Some("id"),
+
                        other => anyhow::bail!("unknown sorting field '{other}'"),
+
                    }
+
                }
+

                Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
                    "select" => op = Some(OperationName::Select),
                    unknown => anyhow::bail!("unknown operation '{}'", unknown),
@@ -83,6 +105,15 @@ impl Args for Options {
            }
        }

+
        select_opts.sort_by = if let Some(field) = field {
+
            inbox::SortBy {
+
                field,
+
                reverse: reverse.unwrap_or(false),
+
            }
+
        } else {
+
            inbox::SortBy::default()
+
        };
+

        let op = match op.ok_or_else(|| anyhow!("an operation must be provided"))? {
            OperationName::Select => Operation::Select { opts: select_opts },
        };
@@ -101,7 +132,12 @@ pub fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()>

            log::enable(context.profile(), "inbox", "select")?;

-
            let mut app = select::App::new(context, opts.mode.clone(), opts.filter.clone());
+
            let mut app = select::App::new(
+
                context,
+
                opts.mode.clone(),
+
                opts.filter.clone(),
+
                opts.sort_by,
+
            );
            let output = Window::default().run(&mut app, 1000 / FPS)?;

            let output = output
modified bin/commands/inbox/select.rs
@@ -17,7 +17,7 @@ use tuirealm::{Application, Frame, NoUserEvent, Sub, SubClause};

use radicle_tui as tui;

-
use tui::cob::inbox::Filter;
+
use tui::cob::inbox::{Filter, SortBy};
use tui::context::Context;

use tui::ui::subscription;
@@ -114,6 +114,7 @@ pub struct App {
    theme: Theme,
    mode: Mode,
    filter: Filter,
+
    sort_by: SortBy,
    output: Option<SelectionExit>,
    quit: bool,
}
@@ -121,13 +122,14 @@ pub struct App {
/// Creates a new application using a tui-realm-application, mounts all
/// components and sets focus to a default one.
impl App {
-
    pub fn new(context: Context, mode: Mode, filter: Filter) -> Self {
+
    pub fn new(context: Context, mode: Mode, filter: Filter, sort_by: SortBy) -> Self {
        Self {
            context,
            pages: PageStack::default(),
            theme: Theme::default(),
            mode,
            filter,
+
            sort_by,
            output: None,
            quit: false,
        }
@@ -138,7 +140,11 @@ impl App {
        app: &mut Application<Cid, Message, NoUserEvent>,
        theme: &Theme,
    ) -> Result<()> {
-
        let home = Box::new(ListView::new(self.mode.clone(), self.filter.clone()));
+
        let home = Box::new(ListView::new(
+
            self.mode.clone(),
+
            self.filter.clone(),
+
            self.sort_by,
+
        ));
        self.pages.push(home, app, &self.context, theme)?;

        Ok(())
modified bin/commands/inbox/select/page.rs
@@ -6,7 +6,7 @@ use tuirealm::{AttrValue, Attribute, Frame, NoUserEvent};

use radicle_tui as tui;

-
use tui::cob::inbox::Filter;
+
use tui::cob::inbox::{Filter, SortBy};
use tui::context::Context;
use tui::ui::layout;
use tui::ui::state::ItemState;
@@ -24,15 +24,17 @@ pub struct ListView {
    active_component: ListCid,
    mode: Mode,
    filter: Filter,
+
    sort_by: SortBy,
    shortcuts: HashMap<ListCid, Widget<Shortcuts>>,
}

impl ListView {
-
    pub fn new(mode: Mode, filter: Filter) -> Self {
+
    pub fn new(mode: Mode, filter: Filter, sort_by: SortBy) -> Self {
        Self {
            active_component: ListCid::NotificationBrowser,
            mode,
            filter,
+
            sort_by,
            shortcuts: HashMap::default(),
        }
    }
@@ -86,20 +88,23 @@ impl ViewPage<Cid, Message> for ListView {
        context: &Context,
        theme: &Theme,
    ) -> Result<()> {
-
        let browser = ui::operation_select(theme, context, self.filter.clone(), None).to_boxed();
+
        let browser = ui::operation_select(theme, context, self.filter.clone(), self.sort_by, None)
+
            .to_boxed();
        self.shortcuts = browser.as_ref().shortcuts();

        match self.mode {
            Mode::Id => {
                let notif_browser =
-
                    ui::id_select(theme, context, self.filter.clone(), None).to_boxed();
+
                    ui::id_select(theme, context, self.filter.clone(), self.sort_by, None)
+
                        .to_boxed();
                self.shortcuts = notif_browser.as_ref().shortcuts();

                app.remount(Cid::List(ListCid::NotificationBrowser), browser, vec![])?;
            }
            Mode::Operation => {
                let notif_browser =
-
                    ui::operation_select(theme, context, self.filter.clone(), None).to_boxed();
+
                    ui::operation_select(theme, context, self.filter.clone(), self.sort_by, None)
+
                        .to_boxed();
                self.shortcuts = notif_browser.as_ref().shortcuts();

                app.remount(Cid::List(ListCid::NotificationBrowser), browser, vec![])?;
modified bin/commands/inbox/select/ui.rs
@@ -10,7 +10,7 @@ use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State};

use radicle_tui as tui;

-
use tui::cob::inbox::Filter;
+
use tui::cob::inbox::{Filter, SortBy};
use tui::context::Context;
use tui::ui::theme::{style, Theme};
use tui::ui::widget::context::{ContextBar, Progress, Shortcuts};
@@ -25,7 +25,12 @@ pub struct NotificationBrowser {
}

impl NotificationBrowser {
-
    pub fn new(theme: &Theme, context: &Context, selected: Option<Notification>) -> Self {
+
    pub fn new(
+
        theme: &Theme,
+
        context: &Context,
+
        sort_by: SortBy,
+
        selected: Option<Notification>,
+
    ) -> Self {
        let header = [
            label::header(""),
            label::header(" ● "),
@@ -42,7 +47,7 @@ impl NotificationBrowser {
            ColumnWidth::Grow,
            ColumnWidth::Fixed(15),
            ColumnWidth::Fixed(10),
-
            ColumnWidth::Fixed(15),
+
            ColumnWidth::Fixed(18),
        ];

        let mut items = vec![];
@@ -54,6 +59,15 @@ impl NotificationBrowser {
            }
        }

+
        match sort_by.field {
+
            "timestamp" => items.sort_by(|a, b| b.timestamp().cmp(a.timestamp())),
+
            "id" => items.sort_by(|a, b| b.id().cmp(a.id())),
+
            _ => {}
+
        }
+
        if sort_by.reverse {
+
            items.reverse();
+
        }
+

        let selected = match selected {
            Some(notif) => {
                Some(NotificationItem::try_from((context.repository(), notif.clone())).unwrap())
@@ -195,9 +209,10 @@ pub fn id_select(
    theme: &Theme,
    context: &Context,
    _filter: Filter,
+
    sort_by: SortBy,
    selected: Option<Notification>,
) -> Widget<IdSelect> {
-
    let browser = Widget::new(NotificationBrowser::new(theme, context, selected));
+
    let browser = Widget::new(NotificationBrowser::new(theme, context, sort_by, selected));

    Widget::new(IdSelect::new(theme.clone(), browser))
}
@@ -206,9 +221,10 @@ pub fn operation_select(
    theme: &Theme,
    context: &Context,
    _filter: Filter,
+
    sort_by: SortBy,
    selected: Option<Notification>,
) -> Widget<OperationSelect> {
-
    let browser = Widget::new(NotificationBrowser::new(theme, context, selected));
+
    let browser = Widget::new(NotificationBrowser::new(theme, context, sort_by, selected));

    Widget::new(OperationSelect::new(theme.clone(), browser))
}
modified bin/terminal/args.rs
@@ -93,6 +93,10 @@ pub fn finish(unparsed: Vec<OsString>) -> anyhow::Result<()> {
    Ok(())
}

+
pub fn string(val: &OsString) -> String {
+
    val.to_string_lossy().to_string()
+
}
+

#[allow(dead_code)]
pub fn rev(val: &OsString) -> anyhow::Result<Rev> {
    let s = val.to_str().ok_or(anyhow!("invalid git rev {val:?}"))?;
modified src/cob/inbox.rs
@@ -7,6 +7,21 @@ use radicle::Profile;
#[derive(Clone, Default, Debug, Eq, PartialEq)]
pub struct Filter {}

+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+
pub struct SortBy {
+
    pub reverse: bool,
+
    pub field: &'static str,
+
}
+

+
impl Default for SortBy {
+
    fn default() -> Self {
+
        Self {
+
            reverse: true,
+
            field: "timestamp",
+
        }
+
    }
+
}
+

pub fn all(repository: &Repository, profile: &Profile) -> Result<Vec<Notification>> {
    let all = profile
        .notifications_mut()?
modified src/context.rs
@@ -1,11 +1,11 @@
use std::fmt::Display;

-
use radicle::node::notifications::Notification;
use radicle::cob::issue::{Issue, IssueId};
use radicle::cob::patch::{Patch, PatchId};
use radicle::crypto::ssh::keystore::{Keystore, MemorySigner};
use radicle::crypto::Signer;
use radicle::identity::{Project, RepoId};
+
use radicle::node::notifications::Notification;
use radicle::profile::env::RAD_PASSPHRASE;
use radicle::storage::git::Repository;
use radicle::storage::{ReadRepository, ReadStorage};