Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
inbox: Implement id selection
Erik Kundt committed 2 years ago
commit 38a7a54bd4dc10d61183d0edd9a0bfbff41cedc3
parent 23dc98ef630966f83c31b9ae4c5f4caa60665084
6 files changed +152 -14
modified bin/commands/inbox.rs
@@ -21,11 +21,12 @@ pub const HELP: Help = Help {
    usage: r#"
Usage

-
    rad-tui inbox select
+
    rad-tui inbox select [<option>...]

Other options

-
    --help               Print help
+
    --mode <MODE>           Set selection mode; see MODE below (default: operation)
+
    --help                  Print help
"#,
};

@@ -44,6 +45,7 @@ pub enum OperationName {

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SelectOptions {
+
    mode: select::Mode,
    filter: inbox::Filter,
}

@@ -53,7 +55,7 @@ impl Args for Options {

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

        while let Some(arg) = parser.next()? {
            match arg {
@@ -61,6 +63,18 @@ impl Args for Options {
                    return Err(Error::Help.into());
                }

+
                // select options.
+
                Long("mode") | Short('m') if op == Some(OperationName::Select) => {
+
                    let val = parser.value()?;
+
                    let val = val.to_str().unwrap_or_default();
+

+
                    select_opts.mode = match val {
+
                        "operation" => select::Mode::Operation,
+
                        "id" => select::Mode::Id,
+
                        unknown => anyhow::bail!("unknown mode '{}'", unknown),
+
                    };
+
                }
+

                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,11 +97,11 @@ pub fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()>
    match options.op {
        Operation::Select { opts } => {
            let profile = terminal::profile()?;
-
            let context = context::Context::new(profile, id)?.with_issues();
+
            let context = context::Context::new(profile, id)?;

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

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

            let output = output
modified bin/commands/inbox/select.rs
@@ -112,22 +112,24 @@ pub struct App {
    context: Context,
    pages: PageStack<Cid, Message>,
    theme: Theme,
-
    quit: bool,
+
    mode: Mode,
    filter: Filter,
    output: Option<SelectionExit>,
+
    quit: bool,
}

/// 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, filter: Filter) -> Self {
+
    pub fn new(context: Context, mode: Mode, filter: Filter) -> Self {
        Self {
            context,
            pages: PageStack::default(),
            theme: Theme::default(),
-
            quit: false,
+
            mode,
            filter,
            output: None,
+
            quit: false,
        }
    }

@@ -136,7 +138,7 @@ impl App {
        app: &mut Application<Cid, Message, NoUserEvent>,
        theme: &Theme,
    ) -> Result<()> {
-
        let home = Box::new(ListView::new(self.filter.clone()));
+
        let home = Box::new(ListView::new(self.mode.clone(), self.filter.clone()));
        self.pages.push(home, app, &self.context, theme)?;

        Ok(())
modified bin/commands/inbox/select/event.rs
@@ -13,7 +13,7 @@ use tui::ui::widget::list::PropertyList;
use tui::ui::widget::Widget;
use tui::{Id, SelectionExit};

-
use super::ui::OperationSelect;
+
use super::ui::{IdSelect, OperationSelect};
use super::{InboxOperation, Message};

/// Since the framework does not know the type of messages that are being
@@ -33,6 +33,49 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<GlobalListener> {
    }
}

+
impl tuirealm::Component<Message, NoUserEvent> for Widget<IdSelect> {
+
    fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
+
        let mut submit = || -> Option<NotificationId> {
+
            match self.perform(Cmd::Submit) {
+
                CmdResult::Submit(state) => {
+
                    let selected = ItemState::try_from(state).ok()?.selected()?;
+
                    let item = self.items().get(selected)?;
+
                    Some(item.id().to_owned())
+
                }
+
                _ => None,
+
            }
+
        };
+

+
        match event {
+
            Event::Keyboard(KeyEvent { code: Key::Up, .. })
+
            | Event::Keyboard(KeyEvent {
+
                code: Key::Char('k'),
+
                ..
+
            }) => {
+
                self.perform(Cmd::Move(MoveDirection::Up));
+
                Some(Message::Tick)
+
            }
+
            Event::Keyboard(KeyEvent {
+
                code: Key::Down, ..
+
            })
+
            | Event::Keyboard(KeyEvent {
+
                code: Key::Char('j'),
+
                ..
+
            }) => {
+
                self.perform(Cmd::Move(MoveDirection::Down));
+
                Some(Message::Tick)
+
            }
+
            Event::Keyboard(KeyEvent {
+
                code: Key::Enter, ..
+
            }) => submit().map(|id| {
+
                let output = SelectionExit::default().with_id(Id::Notification(id));
+
                Message::Quit(Some(output))
+
            }),
+
            _ => None,
+
        }
+
    }
+
}
+

impl tuirealm::Component<Message, NoUserEvent> for Widget<OperationSelect> {
    fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
        let mut submit = || -> Option<NotificationId> {
modified bin/commands/inbox/select/page.rs
@@ -2,7 +2,6 @@ use std::collections::HashMap;

use anyhow::Result;

-
use tui::ui::state::ItemState;
use tuirealm::{AttrValue, Attribute, Frame, NoUserEvent};

use radicle_tui as tui;
@@ -10,26 +9,29 @@ use radicle_tui as tui;
use tui::cob::inbox::Filter;
use tui::context::Context;
use tui::ui::layout;
+
use tui::ui::state::ItemState;
use tui::ui::theme::Theme;
use tui::ui::widget::context::{Progress, Shortcuts};
use tui::ui::widget::Widget;
use tui::ViewPage;

-
use super::{ui, Application, Cid, ListCid, Message};
+
use super::{ui, Application, Cid, ListCid, Message, Mode};

///
/// Home
///
pub struct ListView {
    active_component: ListCid,
+
    mode: Mode,
    filter: Filter,
    shortcuts: HashMap<ListCid, Widget<Shortcuts>>,
}

impl ListView {
-
    pub fn new(filter: Filter) -> Self {
+
    pub fn new(mode: Mode, filter: Filter) -> Self {
        Self {
            active_component: ListCid::NotificationBrowser,
+
            mode,
            filter,
            shortcuts: HashMap::default(),
        }
@@ -87,7 +89,22 @@ impl ViewPage<Cid, Message> for ListView {
        let browser = ui::operation_select(theme, context, self.filter.clone(), None).to_boxed();
        self.shortcuts = browser.as_ref().shortcuts();

-
        app.remount(Cid::List(ListCid::NotificationBrowser), browser, vec![])?;
+
        match self.mode {
+
            Mode::Id => {
+
                let notif_browser =
+
                    ui::id_select(theme, context, self.filter.clone(), 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();
+
                self.shortcuts = notif_browser.as_ref().shortcuts();
+

+
                app.remount(Cid::List(ListCid::NotificationBrowser), browser, vec![])?;
+
            }
+
        };

        app.active(&Cid::List(self.active_component.clone()))?;
        self.update_shortcuts(app, self.active_component.clone())?;
modified bin/commands/inbox/select/ui.rs
@@ -90,6 +90,56 @@ impl WidgetComponent for NotificationBrowser {
    }
}

+
pub struct IdSelect {
+
    theme: Theme,
+
    browser: Widget<NotificationBrowser>,
+
}
+

+
impl IdSelect {
+
    pub fn new(theme: Theme, browser: Widget<NotificationBrowser>) -> Self {
+
        Self { theme, browser }
+
    }
+

+
    pub fn items(&self) -> &Vec<NotificationItem> {
+
        self.browser.items()
+
    }
+

+
    pub fn shortcuts(&self) -> HashMap<ListCid, Widget<Shortcuts>> {
+
        [(
+
            ListCid::NotificationBrowser,
+
            tui::ui::shortcuts(
+
                &self.theme,
+
                vec![
+
                    tui::ui::shortcut(&self.theme, "enter", "select"),
+
                    tui::ui::shortcut(&self.theme, "q", "quit"),
+
                ],
+
            ),
+
        )]
+
        .iter()
+
        .cloned()
+
        .collect()
+
    }
+
}
+

+
impl WidgetComponent for IdSelect {
+
    fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
+
        let focus = properties
+
            .get_or(Attribute::Focus, AttrValue::Flag(false))
+
            .unwrap_flag();
+

+
        self.browser.attr(Attribute::Focus, AttrValue::Flag(focus));
+
        self.browser.view(frame, area);
+
    }
+

+
    fn state(&self) -> State {
+
        self.browser.state()
+
    }
+

+
    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
+
        self.browser.perform(cmd)
+
    }
+
}
+

pub struct OperationSelect {
    theme: Theme,
    browser: Widget<NotificationBrowser>,
@@ -141,6 +191,17 @@ impl WidgetComponent for OperationSelect {
    }
}

+
pub fn id_select(
+
    theme: &Theme,
+
    context: &Context,
+
    _filter: Filter,
+
    selected: Option<Notification>,
+
) -> Widget<IdSelect> {
+
    let browser = Widget::new(NotificationBrowser::new(theme, context, selected));
+

+
    Widget::new(IdSelect::new(theme.clone(), browser))
+
}
+

pub fn operation_select(
    theme: &Theme,
    context: &Context,
modified src/context.rs
@@ -1,5 +1,6 @@
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};