Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
patch: Use container for help page
Erik Kundt committed 2 years ago
commit 4ba0b5e3bf6feb9ef31815977f6980b754cdd082
parent dde5524812e827e053cba81f586b2dc37b65fee3
1 file changed +225 -324
modified bin/commands/patch/select/ui.rs
@@ -19,7 +19,7 @@ use radicle_tui as tui;

use tui::ui::items::{PatchItem, PatchItemFilter};
use tui::ui::span;
-
use tui::ui::widget::container::{Footer, FooterProps, Header, HeaderProps};
+
use tui::ui::widget::container::{Container, Footer, FooterProps, Header, HeaderProps};
use tui::ui::widget::input::{TextField, TextFieldProps, TextFieldState};
use tui::ui::widget::text::{Paragraph, ParagraphProps, ParagraphState};
use tui::ui::widget::{self, TableUtils};
@@ -39,6 +39,9 @@ type BoxedWidget<B> = widget::BoxedWidget<B, State, Action>;
pub struct ListPageProps {
    show_search: bool,
    show_help: bool,
+
    help_progress: usize,
+
    page_size: usize,
+
    focus: bool,
}

impl From<&State> for ListPageProps {
@@ -46,6 +49,9 @@ impl From<&State> for ListPageProps {
        Self {
            show_search: state.ui.show_search,
            show_help: state.help.show,
+
            help_progress: state.help.progress,
+
            page_size: state.ui.page_size,
+
            focus: false,
        }
    }
}
@@ -79,7 +85,64 @@ impl<'a: 'static, B: Backend + 'a> View<State, Action> for ListPage<B> {
            props: ListPageProps::from(state),
            patches: Patches::new(state, action_tx.clone()).to_boxed(),
            search: Search::new(state, action_tx.clone()).to_boxed(),
-
            help: Help::new(state, action_tx.clone()).to_boxed(),
+
            help: Container::new(state, action_tx.clone())
+
                .header(
+
                    Header::new(state, action_tx.clone())
+
                        .on_update(|state| {
+
                            let props = ListPageProps::from(state);
+

+
                            HeaderProps::default()
+
                                .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
+
                                .focus(props.focus)
+
                                .to_boxed()
+
                        })
+
                        .to_boxed(),
+
                )
+
                .content(
+
                    Paragraph::new(state, action_tx.clone())
+
                        .on_update(|state| {
+
                            let props = ListPageProps::from(state);
+

+
                            ParagraphProps::default()
+
                                .text(&help_text())
+
                                .page_size(props.page_size)
+
                                .focus(props.focus)
+
                                .to_boxed()
+
                        })
+
                        .on_change(|state, action_tx| {
+
                            state.downcast_ref::<ParagraphState>().and_then(|state| {
+
                                action_tx
+
                                    .send(Action::ScrollHelp {
+
                                        progress: state.progress,
+
                                    })
+
                                    .ok()
+
                            });
+
                        })
+
                        .to_boxed(),
+
                )
+
                .footer(
+
                    Footer::new(state, action_tx.clone())
+
                        .on_update(|state| {
+
                            let props = ListPageProps::from(state);
+

+
                            FooterProps::default()
+
                                .columns(
+
                                    [
+
                                        Column::new(Text::raw(""), Constraint::Fill(1)),
+
                                        Column::new(
+
                                            span::default(format!("{}%", props.help_progress))
+
                                                .dim(),
+
                                            Constraint::Min(4),
+
                                        ),
+
                                    ]
+
                                    .to_vec(),
+
                                )
+
                                .focus(props.focus)
+
                                .to_boxed()
+
                        })
+
                        .to_boxed(),
+
                )
+
                .to_boxed(),
            shortcuts: Shortcuts::new(state, action_tx.clone())
                .on_update(|state| {
                    ShortcutsProps::default()
@@ -115,7 +178,17 @@ impl<'a: 'static, B: Backend + 'a> View<State, Action> for ListPage<B> {
        if self.props.show_search {
            self.search.handle_key_event(key);
        } else if self.props.show_help {
-
            self.help.handle_key_event(key);
+
            match key {
+
                Key::Esc | Key::Ctrl('c') => {
+
                    let _ = self.action_tx.send(Action::Exit { selection: None });
+
                }
+
                Key::Char('?') => {
+
                    let _ = self.action_tx.send(Action::CloseHelp);
+
                }
+
                _ => {
+
                    self.help.handle_key_event(key);
+
                }
+
            }
        } else {
            match key {
                Key::Esc | Key::Ctrl('c') => {
@@ -139,23 +212,33 @@ impl<'a: 'static, B> Widget<B, State, Action> for ListPage<B>
where
    B: Backend + 'a,
{
-
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, __props: Option<&dyn Any>) {
+
    fn render(&self, frame: &mut ratatui::Frame, _area: Rect, props: Option<&dyn Any>) {
+
        let props = props
+
            .and_then(|props| props.downcast_ref::<ListPageProps>())
+
            .unwrap_or(&self.props);
+

        let area = frame.size();
        let layout = tui::ui::layout::default_page(area, 0u16, 1u16);

-
        if self.props.show_search {
+
        let page_size = area.height.saturating_sub(6) as usize;
+

+
        if props.show_search {
            let component_layout = Layout::vertical([Constraint::Min(1), Constraint::Length(2)])
                .split(layout.component);

            self.patches.render(frame, component_layout[0], None);
            self.search.render(frame, component_layout[1], None);
-
        } else if self.props.show_help {
+
        } else if props.show_help {
            self.help.render(frame, layout.component, None);
        } else {
            self.patches.render(frame, layout.component, None);
        }

        self.shortcuts.render(frame, layout.shortcuts, None);
+

+
        if page_size != props.page_size {
+
            let _ = self.action_tx.send(Action::PageSize(page_size));
+
        }
    }
}

@@ -491,12 +574,8 @@ where
            .and_then(|props| props.downcast_ref::<PatchesProps>())
            .unwrap_or(&self.props);

-
        let header_height = 3_usize;
-

-
        let page_size = if props.show_search {
+
        if props.show_search {
            self.table.render(frame, area, None);
-

-
            (area.height as usize).saturating_sub(header_height)
        } else {
            let layout = Layout::vertical([Constraint::Min(1), Constraint::Length(3)]).split(area);

@@ -506,12 +585,6 @@ where
                layout[1],
                Some(&FooterProps::default().columns(Self::build_footer(props, props.selected))),
            );
-

-
            (area.height as usize).saturating_sub(header_height)
-
        };
-

-
        if page_size != props.page_size {
-
            let _ = self.action_tx.send(Action::PageSize(page_size));
        }
    }
}
@@ -600,314 +673,142 @@ where
    }
}

-
#[derive(Clone)]
-
pub struct HelpProps<'a> {
-
    content: Text<'a>,
-
    focus: bool,
-
    page_size: usize,
-
    progress: usize,
-
}
-

-
impl<'a> From<&State> for HelpProps<'a> {
-
    fn from(state: &State) -> Self {
-
        let content = Text::from(
-
            [
-
                Line::from(Span::raw("Generic keybindings").cyan()),
-
                Line::raw(""),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "↑,k")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor one line up").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "↓,j")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor one line down").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "PageUp")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor one page up").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "PageDown")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor one page down").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "Home")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor to the first line").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "End")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("move cursor to the last line").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::raw(""),
-
                Line::from(Span::raw("Specific keybindings").cyan()),
-
                Line::raw(""),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "enter")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Select patch (if --mode id)").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "enter")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Show patch").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "c")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Checkout patch").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "d")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Show patch diff").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "/")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Search").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "?")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Show help").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "Esc")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("Quit / cancel").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::raw(""),
-
                Line::from(Span::raw("Searching").cyan()),
-
                Line::raw(""),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "Pattern")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("is:<state> | is:authored | authors:[<did>, <did>] | <search>")
-
                            .gray()
-
                            .dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
                Line::from(
-
                    [
-
                        Span::raw(format!("{key:>10}", key = "Example")).gray(),
-
                        Span::raw(" "),
-
                        Span::raw("is:open is:authored improve").gray().dim(),
-
                    ]
-
                    .to_vec(),
-
                ),
-
            ]
-
            .to_vec(),
-
        );
-

-
        Self {
-
            content,
-
            focus: false,
-
            page_size: state.ui.page_size,
-
            progress: state.help.progress,
-
        }
-
    }
-
}
-

-
impl<'a> Properties for HelpProps<'a> {}
-

-
pub struct Help<'a, B: Backend> {
-
    /// Internal properties
-
    props: HelpProps<'a>,
-
    /// Message sender
-
    action_tx: UnboundedSender<Action>,
-
    /// Custom update handler
-
    on_update: Option<UpdateCallback<State>>,
-
    /// Additional custom event handler
-
    on_change: Option<EventCallback<Action>>,
-
    /// Container header
-
    header: BoxedWidget<B>,
-
    /// Content widget
-
    content: BoxedWidget<B>,
-
    /// Container footer
-
    footer: BoxedWidget<B>,
-
}
-

-
impl<'a, B: Backend> View<State, Action> for Help<'a, B> {
-
    fn new(state: &State, action_tx: UnboundedSender<Action>) -> Self
-
    where
-
        Self: Sized,
-
    {
-
        Self {
-
            action_tx: action_tx.clone(),
-
            props: HelpProps::from(state),
-
            header: Header::new(state, action_tx.clone()).to_boxed(),
-
            content: Paragraph::new(state, action_tx.clone())
-
                .on_change(|props, action_tx| {
-
                    props.downcast_ref::<ParagraphProps>().and_then(|props| {
-
                        action_tx
-
                            .send(Action::ScrollHelp {
-
                                progress: props.progress,
-
                            })
-
                            .ok()
-
                    });
-
                })
-
                .on_update(|state| {
-
                    let props = HelpProps::from(state);
-

-
                    ParagraphProps::default()
-
                        .text(&props.content)
-
                        .page_size(props.page_size)
-
                        .focus(props.focus)
-
                        .to_boxed()
-
                })
-
                .on_change(|state, action_tx| {
-
                    state.downcast_ref::<ParagraphState>().and_then(|state| {
-
                        action_tx
-
                            .send(Action::ScrollHelp {
-
                                progress: state.progress,
-
                            })
-
                            .ok()
-
                    });
-
                })
-
                .to_boxed(),
-
            footer: Footer::new(state, action_tx).to_boxed(),
-
            on_update: None,
-
            on_change: None,
-
        }
-
    }
-

-
    fn on_update(mut self, callback: UpdateCallback<State>) -> Self {
-
        self.on_update = Some(callback);
-
        self
-
    }
-

-
    fn on_change(mut self, callback: EventCallback<Action>) -> Self {
-
        self.on_change = Some(callback);
-
        self
-
    }
-

-
    fn update(&mut self, state: &State) {
-
        self.props = HelpProps::from(state);
-

-
        self.header.update(state);
-
        self.content.update(state);
-
        self.footer.update(state);
-
    }
-

-
    fn handle_key_event(&mut self, key: termion::event::Key) {
-
        match key {
-
            Key::Esc => {
-
                let _ = self.action_tx.send(Action::Exit { selection: None });
-
            }
-
            Key::Char('?') => {
-
                let _ = self.action_tx.send(Action::CloseHelp);
-
            }
-
            _ => {
-
                self.content.handle_key_event(key);
-
            }
-
        }
-
    }
-
}
-

-
impl<'a: 'static, B> Widget<B, State, Action> for Help<'a, B>
-
where
-
    B: Backend,
-
{
-
    fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) {
-
        let props = props
-
            .and_then(|props| props.downcast_ref::<HelpProps>())
-
            .unwrap_or(&self.props);
-

-
        let [header_area, content_area, footer_area] = Layout::vertical([
-
            Constraint::Length(3),
-
            Constraint::Min(1),
-
            Constraint::Length(3),
-
        ])
-
        .areas(area);
-
        let progress = span::default(format!("{}%", props.progress)).dim();
-

-
        self.header.render(
-
            frame,
-
            header_area,
-
            Some(
-
                &HeaderProps::default()
-
                    .columns([Column::new(" Help ", Constraint::Fill(1))].to_vec())
-
                    .focus(props.focus),
+
fn help_text() -> Text<'static> {
+
    Text::from(
+
        [
+
            Line::from(Span::raw("Generic keybindings").cyan()),
+
            Line::raw(""),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "↑,k")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor one line up").gray().dim(),
+
                ]
+
                .to_vec(),
            ),
-
        );
-

-
        self.content.render(
-
            frame,
-
            content_area,
-
            Some(
-
                &ParagraphProps::default()
-
                    .text(&props.content)
-
                    .page_size(props.page_size)
-
                    .focus(props.focus),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "↓,j")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor one line down").gray().dim(),
+
                ]
+
                .to_vec(),
            ),
-
        );
-

-
        self.footer.render(
-
            frame,
-
            footer_area,
-
            Some(
-
                &FooterProps::default()
-
                    .columns(
-
                        [
-
                            Column::new(Text::raw(""), Constraint::Fill(1)),
-
                            Column::new(progress, Constraint::Min(4)),
-
                        ]
-
                        .to_vec(),
-
                    )
-
                    .focus(props.focus),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "PageUp")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor one page up").gray().dim(),
+
                ]
+
                .to_vec(),
            ),
-
        );
-

-
        let page_size = content_area.height as usize;
-
        if page_size != props.page_size {
-
            let _ = self.action_tx.send(Action::PageSize(page_size));
-
        }
-
    }
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "PageDown")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor one page down").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "Home")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor to the first line").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "End")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("move cursor to the last line").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::raw(""),
+
            Line::from(Span::raw("Specific keybindings").cyan()),
+
            Line::raw(""),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "enter")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Select patch (if --mode id)").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "enter")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Show patch").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "c")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Checkout patch").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "d")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Show patch diff").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "/")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Search").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "?")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Show help").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "Esc")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("Quit / cancel").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::raw(""),
+
            Line::from(Span::raw("Searching").cyan()),
+
            Line::raw(""),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "Pattern")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("is:<state> | is:authored | authors:[<did>, <did>] | <search>")
+
                        .gray()
+
                        .dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::from(
+
                [
+
                    Span::raw(format!("{key:>10}", key = "Example")).gray(),
+
                    Span::raw(" "),
+
                    Span::raw("is:open is:authored improve").gray().dim(),
+
                ]
+
                .to_vec(),
+
            ),
+
            Line::raw(""),
+
            Line::raw(""),
+
        ]
+
        .to_vec(),
+
    )
}