Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Refresh issue and patch browser on page leave
Erik Kundt committed 2 years ago
commit dae897f8cc7b4aa4c4a313d1e67b6bc79af05070
parent aa0fe2336c7cd6290bf8b315a1d1489c154a9faa
6 files changed +105 -55
modified src/app.rs
@@ -64,7 +64,9 @@ pub enum Cid {

/// Messages handled by this application.
#[derive(Clone, Debug, Eq, PartialEq)]
-
pub enum HomeMessage {}
+
pub enum HomeMessage {
+
    RefreshIssues(Option<IssueId>),
+
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IssueCobMessage {
@@ -85,7 +87,7 @@ pub enum IssueMessage {
    Cob(IssueCobMessage),
    OpenForm,
    HideForm,
-
    Leave,
+
    Leave(Option<IssueId>),
}

#[derive(Clone, Debug, Eq, PartialEq)]
@@ -192,17 +194,6 @@ impl App {
                Ok(())
            }
        }
-

-
        // if let Some(issue) = cob::issue::find(repo, &id)? {
-
        //     let view = Box::new(IssuePage::new(&self.context, theme, (id, issue)));
-
        //     self.pages.push(view, app, &self.context, theme)?;
-

-
        //     Ok(())
-
        // } else {
-
        //     Err(anyhow::anyhow!(
-
        //         "Could not mount 'page::IssueView'. Issue not found."
-
        //     ))
-
        // }
    }

    fn process(
@@ -250,9 +241,9 @@ impl App {
                self.view_issue(app, id, &theme)?;
                Ok(None)
            }
-
            Message::Issue(IssueMessage::Leave) => {
+
            Message::Issue(IssueMessage::Leave(id)) => {
                self.pages.pop(app)?;
-
                Ok(None)
+
                Ok(Some(Message::Home(HomeMessage::RefreshIssues(id))))
            }
            Message::Patch(PatchMessage::Show(id)) => {
                self.view_patch(app, id, &theme)?;
modified src/app/event.rs
@@ -53,9 +53,15 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<AppHeader> {
impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::LargeList> {
    fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
        match event {
-
            Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
-
                Some(Message::Issue(IssueMessage::Leave))
-
            }
+
            Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => match self.state() {
+
                State::Tup2((StateValue::Usize(selected), StateValue::Usize(_))) => {
+
                    let item = self.items().get(selected)?;
+
                    Some(Message::Issue(IssueMessage::Leave(Some(
+
                        item.id().to_owned(),
+
                    ))))
+
                }
+
                _ => None,
+
            },
            Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
                let result = self.perform(Cmd::Move(MoveDirection::Up));
                match result {
modified src/app/page.rs
@@ -7,14 +7,16 @@ use radicle::cob::patch::{Patch, PatchId};

use radicle_tui::cob;
use radicle_tui::ui::widget::common::context::{Progress, Shortcuts};
-
use tuirealm::{Frame, NoUserEvent, StateValue, Sub, SubClause};
+
use tuirealm::{AttrValue, Attribute, Frame, NoUserEvent, StateValue, Sub, SubClause};

use radicle_tui::ui::context::Context;
use radicle_tui::ui::layout;
use radicle_tui::ui::theme::Theme;
use radicle_tui::ui::widget::{self, Widget};

-
use super::{subscription, Application, Cid, HomeCid, IssueCid, IssueMessage, Message, PatchCid};
+
use super::{
+
    subscription, Application, Cid, HomeCid, HomeMessage, IssueCid, IssueMessage, Message, PatchCid,
+
};

/// `tuirealm`'s event and prop system is designed to work with flat component hierarchies.
/// Building deep nested component hierarchies would need a lot more additional effort to
@@ -74,6 +76,19 @@ impl HomeView {
        }
    }

+
    fn activate(
+
        &mut self,
+
        app: &mut Application<Cid, Message, NoUserEvent>,
+
        cid: HomeCid,
+
    ) -> Result<()> {
+
        self.active_component = cid;
+
        let cid = Cid::Home(self.active_component.clone());
+
        app.active(&cid)?;
+
        app.attr(&cid, Attribute::Focus, AttrValue::Flag(true))?;
+

+
        Ok(())
+
    }
+

    fn build_shortcuts(theme: &Theme) -> HashMap<HomeCid, Widget<Shortcuts>> {
        [
            (
@@ -182,12 +197,15 @@ impl ViewPage for HomeView {
        context: &Context,
        theme: &Theme,
    ) -> Result<()> {
+
        let issue = context.issues().first().cloned();
+
        let patch = context.patches().first().cloned();
+

        let navigation = widget::home::navigation(theme);
        let header = widget::common::app_header(context, theme, Some(navigation)).to_boxed();

        let dashboard = widget::home::dashboard(context, theme).to_boxed();
-
        let issue_browser = widget::home::issues(context, theme).to_boxed();
-
        let patch_browser = widget::home::patches(context, theme).to_boxed();
+
        let issue_browser = widget::home::issues(context, theme, issue).to_boxed();
+
        let patch_browser = widget::home::patches(context, theme, patch).to_boxed();

        app.remount(Cid::Home(HomeCid::Header), header, vec![])?;

@@ -220,13 +238,25 @@ impl ViewPage for HomeView {
        theme: &Theme,
        message: Message,
    ) -> Result<Option<Message>> {
-
        if let Message::NavigationChanged(index) = message {
-
            self.active_component = HomeCid::from(index as usize);
+
        match message {
+
            Message::NavigationChanged(index) => {
+
                self.activate(app, HomeCid::from(index as usize))?;
+
                self.update_shortcuts(app, self.active_component.clone())?;
+
            }
+
            Message::Home(HomeMessage::RefreshIssues(id)) => {
+
                let selected = match id {
+
                    Some(id) => {
+
                        cob::issue::find(context.repository(), &id)?.map(|issue| (id, issue))
+
                    }
+
                    _ => None,
+
                };

-
            let active_component = Cid::Home(self.active_component.clone());
-
            app.active(&active_component)?;
+
                let issue_browser = widget::home::issues(context, theme, selected).to_boxed();
+
                app.remount(Cid::Home(HomeCid::IssueBrowser), issue_browser, vec![])?;

-
            self.update_shortcuts(app, self.active_component.clone())?;
+
                self.activate(app, HomeCid::IssueBrowser)?;
+
            }
+
            _ => {}
        }

        self.update_context(app, context, theme, self.active_component.clone())?;
@@ -299,7 +329,9 @@ impl IssuePage {
        cid: IssueCid,
    ) -> Result<()> {
        self.active_component = cid;
-
        app.active(&Cid::Issue(self.active_component.clone()))?;
+
        let cid = Cid::Issue(self.active_component.clone());
+
        app.active(&cid)?;
+
        app.attr(&cid, Attribute::Focus, AttrValue::Flag(true))?;

        Ok(())
    }
@@ -469,8 +501,8 @@ impl ViewPage for IssuePage {
                let repo = context.repository();

                if let Some(issue) = cob::issue::find(repo, &id)? {
-
                    let list =
-
                        widget::issue::list(context, theme, Some((id, issue.clone()))).to_boxed();
+
                    self.issue = Some((id, issue.clone()));
+
                    let list = widget::issue::list(context, theme, self.issue.clone()).to_boxed();
                    let comments = issue.comments().collect::<Vec<_>>();

                    let details = widget::issue::details(
@@ -514,11 +546,9 @@ impl ViewPage for IssuePage {

                app.unsubscribe(&Cid::GlobalListener, subscription::global_clause())?;

-
                self.activate(app, IssueCid::Form)?;
-
                self.update_shortcuts(app, IssueCid::Form)?;
+
                return Ok(Some(Message::Issue(IssueMessage::Focus(IssueCid::Form))));
            }
            Message::Issue(IssueMessage::HideForm) => {
-
                app.blur()?;
                app.umount(&Cid::Issue(IssueCid::Form))?;

                let list = widget::issue::list(context, theme, self.issue.clone()).to_boxed();
@@ -529,12 +559,10 @@ impl ViewPage for IssuePage {
                    Sub::new(subscription::global_clause(), SubClause::Always),
                )?;

-
                self.activate(app, IssueCid::List)?;
-
                self.update_shortcuts(app, IssueCid::List)?;
-

                if self.issue.is_none() {
-
                    return Ok(Some(Message::Issue(IssueMessage::Leave)));
+
                    return Ok(Some(Message::Issue(IssueMessage::Leave(None))));
                }
+
                return Ok(Some(Message::Issue(IssueMessage::Focus(IssueCid::List))));
            }
            _ => {}
        }
modified src/ui/cob.rs
@@ -103,6 +103,12 @@ impl PatchItem {
    }
}

+
impl PartialEq for PatchItem {
+
    fn eq(&self, other: &Self) -> bool {
+
        self.id == other.id
+
    }
+
}
+

impl TryFrom<(&Profile, &Repository, PatchId, Patch)> for PatchItem {
    type Error = anyhow::Error;

modified src/ui/widget/common/list.rs
@@ -182,7 +182,7 @@ impl WidgetComponent for PropertyTable {
/// A table component that can display a list of [`TableItem`]s.
pub struct Table<V, const W: usize>
where
-
    V: TableItem<W> + Clone,
+
    V: TableItem<W> + Clone + PartialEq,
{
    /// Items hold by this model.
    items: Vec<V>,
@@ -198,19 +198,25 @@ where

impl<V, const W: usize> Table<V, W>
where
-
    V: TableItem<W> + Clone,
+
    V: TableItem<W> + Clone + PartialEq,
{
    pub fn new(
        items: &[V],
+
        selected: Option<V>,
        header: [Widget<Label>; W],
        widths: [ColumnWidth; W],
        theme: Theme,
    ) -> Self {
+
        let selected = match selected {
+
            Some(item) => items.iter().position(|i| i == &item),
+
            _ => None,
+
        };
+

        Self {
            items: items.to_vec(),
            header,
            widths,
-
            state: ItemState::new(Some(0), items.len()),
+
            state: ItemState::new(selected, items.len()),
            theme,
        }
    }
@@ -218,7 +224,7 @@ where

impl<V, const W: usize> WidgetComponent for Table<V, W>
where
-
    V: TableItem<W> + Clone,
+
    V: TableItem<W> + Clone + PartialEq,
{
    fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
        let highlight = properties
modified src/ui/widget/home.rs
@@ -1,3 +1,5 @@
+
use radicle::cob::issue::{Issue, IssueId};
+
use radicle::cob::patch::{Patch, PatchId};
use tuirealm::command::{Cmd, CmdResult};
use tuirealm::tui::layout::Rect;
use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State};
@@ -8,7 +10,6 @@ use super::common::list::{ColumnWidth, Table};

use super::{Widget, WidgetComponent};

-
use crate::cob;
use crate::ui::cob::{IssueItem, PatchItem};
use crate::ui::context::Context;
use crate::ui::theme::Theme;
@@ -43,7 +44,7 @@ pub struct IssueBrowser {
}

impl IssueBrowser {
-
    pub fn new(context: &Context, theme: &Theme) -> Self {
+
    pub fn new(context: &Context, theme: &Theme, selected: Option<(IssueId, Issue)>) -> Self {
        let header = [
            common::label(" ● "),
            common::label("ID"),
@@ -76,7 +77,10 @@ impl IssueBrowser {
        items.sort_by(|a, b| b.timestamp().cmp(a.timestamp()));
        items.sort_by(|a, b| b.state().cmp(a.state()));

-
        let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
+
        let selected =
+
            selected.map(|(id, issue)| IssueItem::from((context.profile(), repo, id, issue)));
+

+
        let table = Widget::new(Table::new(&items, selected, header, widths, theme.clone()))
            .highlight(theme.colors.item_list_highlighted_bg);

        Self { items, table }
@@ -112,7 +116,7 @@ pub struct PatchBrowser {
}

impl PatchBrowser {
-
    pub fn new(context: &Context, theme: &Theme) -> Self {
+
    pub fn new(context: &Context, theme: &Theme, selected: Option<(PatchId, Patch)>) -> Self {
        let header = [
            common::label(" ● "),
            common::label("ID"),
@@ -138,18 +142,19 @@ impl PatchBrowser {
        let repo = context.repository();
        let mut items = vec![];

-
        if let Ok(patches) = cob::patch::all(repo) {
-
            for (id, patch) in patches {
-
                if let Ok(item) = PatchItem::try_from((context.profile(), repo, id, patch)) {
-
                    items.push(item);
-
                }
+
        for (id, patch) in context.patches() {
+
            if let Ok(item) = PatchItem::try_from((context.profile(), repo, *id, patch.clone())) {
+
                items.push(item);
            }
        }

        items.sort_by(|a, b| b.timestamp().cmp(a.timestamp()));
        items.sort_by(|a, b| a.state().cmp(b.state()));

-
        let table = Widget::new(Table::new(&items, header, widths, theme.clone()))
+
        let selected = selected
+
            .map(|(id, patch)| PatchItem::try_from((context.profile(), repo, id, patch)).unwrap());
+

+
        let table = Widget::new(Table::new(&items, selected, header, widths, theme.clone()))
            .highlight(theme.colors.item_list_highlighted_bg);

        Self { items, table }
@@ -209,10 +214,18 @@ pub fn dashboard(context: &Context, theme: &Theme) -> Widget<Dashboard> {
    Widget::new(dashboard)
}

-
pub fn patches(context: &Context, theme: &Theme) -> Widget<PatchBrowser> {
-
    Widget::new(PatchBrowser::new(context, theme))
+
pub fn patches(
+
    context: &Context,
+
    theme: &Theme,
+
    selected: Option<(PatchId, Patch)>,
+
) -> Widget<PatchBrowser> {
+
    Widget::new(PatchBrowser::new(context, theme, selected))
}

-
pub fn issues(context: &Context, theme: &Theme) -> Widget<IssueBrowser> {
-
    Widget::new(IssueBrowser::new(context, theme))
+
pub fn issues(
+
    context: &Context,
+
    theme: &Theme,
+
    selected: Option<(IssueId, Issue)>,
+
) -> Widget<IssueBrowser> {
+
    Widget::new(IssueBrowser::new(context, theme, selected))
}