From 0361ca38187bd43dd74396c8ae6df437d7444f37 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Wed, 17 May 2023 12:28:42 +0200 Subject: [PATCH] tui: Use table model in patch browser --- radicle-tui/src/app.rs | 52 ++++++------- radicle-tui/src/app/event.rs | 33 +++------ radicle-tui/src/ui/widget/common.rs | 111 ++++------------------------ radicle-tui/src/ui/widget/home.rs | 106 +++++++++++++++++++++----- radicle-tui/src/ui/widget/patch.rs | 4 +- 5 files changed, 140 insertions(+), 166 deletions(-) diff --git a/radicle-tui/src/app.rs b/radicle-tui/src/app.rs index 5f99f87a..fb8accb4 100644 --- a/radicle-tui/src/app.rs +++ b/radicle-tui/src/app.rs @@ -4,14 +4,14 @@ pub mod subscription; use anyhow::Result; +use radicle::cob::patch::{PatchId, Patches}; use radicle::identity::{Id, Project}; use radicle::profile::Profile; +use radicle::storage::ReadStorage; use tuirealm::application::PollStrategy; use tuirealm::{Application, Frame, NoUserEvent}; -use radicle_tui::cob::patch::{self}; - use radicle_tui::ui; use radicle_tui::ui::theme::{self, Theme}; use radicle_tui::Tui; @@ -45,13 +45,11 @@ pub enum Cid { /// Messages handled by this application. #[derive(Debug, Eq, PartialEq)] -pub enum HomeMessage { - PatchChanged(usize), -} +pub enum HomeMessage {} #[derive(Debug, Eq, PartialEq)] pub enum PatchMessage { - Show, + Show(PatchId), Leave, } @@ -99,8 +97,7 @@ impl App { app: &mut Application, theme: &Theme, ) -> Result<()> { - let patches = patch::load_all(&self.context.profile, self.context.id); - let home = Box::new(HomeView::new(patches)); + let home = Box::::default(); self.pages.push(home, app, &self.context, theme)?; Ok(()) @@ -109,29 +106,26 @@ impl App { fn view_patch( &mut self, app: &mut Application, + id: PatchId, theme: &Theme, ) -> Result<()> { - let page = self.pages.peek_mut()?; - let state = page.state().unwrap_map(); - let patches = state - .and_then(|mut values| values.remove("patches")) - .and_then(|value| value.unwrap_patches()); + let repo = self + .context + .profile + .storage + .repository(self.context.id) + .unwrap(); + let patches = Patches::open(&repo).unwrap(); - match patches { - Some((patches, selection)) => match patches.get(selection) { - Some((id, patch)) => { - let view = Box::new(PatchView::new((*id, patch.clone()))); - self.pages.push(view, app, &self.context, theme)?; + if let Some(patch) = patches.get(&id)? { + let view = Box::new(PatchView::new((id, patch))); + self.pages.push(view, app, &self.context, theme)?; - Ok(()) - } - None => Err(anyhow::anyhow!( - "Could not mount 'page::PatchView'. Patch not found." - )), - }, - None => Err(anyhow::anyhow!( - "Could not mount 'page::PatchView'. No state value for 'patches' found." - )), + Ok(()) + } else { + Err(anyhow::anyhow!( + "Could not mount 'page::PatchView'. Patch not found." + )) } } } @@ -159,8 +153,8 @@ impl Tui for App { let theme = theme::default_dark(); for message in messages { match message { - Message::Patch(PatchMessage::Show) => { - self.view_patch(app, &theme)?; + Message::Patch(PatchMessage::Show(id)) => { + self.view_patch(app, id, &theme)?; } Message::Patch(PatchMessage::Leave) => { self.pages.pop(app)?; diff --git a/radicle-tui/src/app/event.rs b/radicle-tui/src/app/event.rs index 3239341a..3b0e8e57 100644 --- a/radicle-tui/src/app/event.rs +++ b/radicle-tui/src/app/event.rs @@ -1,21 +1,16 @@ -use radicle::cob::patch::{Patch, PatchId}; - use tuirealm::command::{Cmd, CmdResult, Direction as MoveDirection}; use tuirealm::event::{Event, Key, KeyEvent}; use tuirealm::{MockComponent, NoUserEvent, State, StateValue}; use radicle_tui::ui::widget::common::container::{GlobalListener, LabeledContainer, Tabs}; use radicle_tui::ui::widget::common::context::{ContextBar, Shortcuts}; - use radicle_tui::ui::widget::common::list::PropertyList; - -use radicle_tui::ui::widget::common::Browser; -use radicle_tui::ui::widget::home::{Dashboard, IssueBrowser}; +use radicle_tui::ui::widget::home::{Dashboard, IssueBrowser, PatchBrowser}; use radicle_tui::ui::widget::patch; use radicle_tui::ui::widget::Widget; -use super::{HomeMessage, Message, PatchMessage}; +use super::{Message, PatchMessage}; /// Since the framework does not know the type of messages that are being /// passed around in the app, the following handlers need to be implemented for @@ -50,28 +45,24 @@ impl tuirealm::Component for Widget { } } -impl tuirealm::Component for Widget> { +impl tuirealm::Component for Widget { fn on(&mut self, event: Event) -> Option { match event { Event::Keyboard(KeyEvent { code: Key::Up, .. }) => { - match self.perform(Cmd::Move(MoveDirection::Up)) { - CmdResult::Changed(State::One(StateValue::Usize(index))) => { - Some(Message::Home(HomeMessage::PatchChanged(index))) - } - _ => Some(Message::Tick), - } + self.perform(Cmd::Move(MoveDirection::Up)); + Some(Message::Tick) } Event::Keyboard(KeyEvent { code: Key::Down, .. - }) => match self.perform(Cmd::Move(MoveDirection::Down)) { - CmdResult::Changed(State::One(StateValue::Usize(index))) => { - Some(Message::Home(HomeMessage::PatchChanged(index))) - } - _ => Some(Message::Tick), - }, + }) => { + self.perform(Cmd::Move(MoveDirection::Down)); + Some(Message::Tick) + } Event::Keyboard(KeyEvent { code: Key::Enter, .. - }) => Some(Message::Patch(PatchMessage::Show)), + }) => self + .selected_item() + .map(|item| Message::Patch(PatchMessage::Show(item.id()))), _ => None, } } diff --git a/radicle-tui/src/ui/widget/common.rs b/radicle-tui/src/ui/widget/common.rs index a0638101..31aaf7d0 100644 --- a/radicle-tui/src/ui/widget/common.rs +++ b/radicle-tui/src/ui/widget/common.rs @@ -3,63 +3,20 @@ pub mod context; pub mod label; pub mod list; -use std::marker::PhantomData; - -use radicle::Profile; - -use tuirealm::command::{Cmd, CmdResult}; -use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, TextSpan}; -use tuirealm::tui::layout::Rect; -use tuirealm::{Frame, MockComponent, Props, State}; +use tuirealm::props::{AttrValue, Attribute}; +use tuirealm::MockComponent; use container::{GlobalListener, Header, LabeledContainer, Tabs}; use context::{Shortcut, Shortcuts}; use label::Label; -use list::{List, Property, PropertyList, Table}; +use list::{Property, PropertyList}; -use super::{Widget, WidgetComponent}; +use self::list::{ColumnWidth, TableModel}; + +use super::Widget; -use crate::ui::layout; use crate::ui::theme::Theme; -pub struct Browser { - list: Widget, - shortcuts: Widget, - phantom: PhantomData, -} - -impl Browser { - pub fn new(list: Widget
, shortcuts: Widget) -> Self { - Self { - list, - shortcuts, - phantom: PhantomData, - } - } -} - -impl WidgetComponent for Browser { - fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) { - let shortcuts_h = self - .shortcuts - .query(Attribute::Height) - .unwrap_or(AttrValue::Size(0)) - .unwrap_size(); - let layout = layout::root_component(area, shortcuts_h); - - self.list.view(frame, layout[0]); - self.shortcuts.view(frame, layout[1]); - } - - fn state(&self) -> State { - State::None - } - - fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult { - self.list.perform(cmd) - } -} - pub fn global_listener() -> Widget { Widget::new(GlobalListener::default()) } @@ -76,34 +33,14 @@ pub fn label(content: &str) -> Widget
{ - let items = items.iter().map(|item| item.row(theme, profile)).collect(); - - let header = table_header(theme, labels, widths); - let table = Table::new(header); - - let widths = AttrValue::Payload(PropPayload::Vec( - widths.iter().map(|w| PropValue::U16(*w)).collect(), - )); - - Widget::new(table) - .content(AttrValue::Table(items)) - .custom("widths", widths) - .background(theme.colors.labeled_container_bg) - .highlight(theme.colors.item_list_highlighted_bg) -} diff --git a/radicle-tui/src/ui/widget/home.rs b/radicle-tui/src/ui/widget/home.rs index 1f6b5d98..fbaf7a15 100644 --- a/radicle-tui/src/ui/widget/home.rs +++ b/radicle-tui/src/ui/widget/home.rs @@ -1,18 +1,22 @@ -use radicle::cob::patch::{Patch, PatchId}; +use radicle::prelude::{Id, Project}; +use radicle::storage::ReadStorage; use radicle::Profile; -use radicle::prelude::{Id, Project}; +use radicle::cob::patch::Patches; + use tuirealm::command::{Cmd, CmdResult}; use tuirealm::tui::layout::Rect; use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State}; -use super::{Widget, WidgetComponent}; - +use super::common; use super::common::container::{LabeledContainer, Tabs}; use super::common::context::Shortcuts; use super::common::label::Label; -use super::common::{self, Browser}; +use super::common::list::{ColumnWidth, Table}; +use super::{Widget, WidgetComponent}; + +use crate::ui::cob::PatchItem; use crate::ui::layout; use crate::ui::theme::Theme; @@ -88,6 +92,84 @@ impl WidgetComponent for IssueBrowser { } } +pub struct PatchBrowser { + table: Widget>, + shortcuts: Widget, +} + +impl PatchBrowser { + pub fn new(profile: &Profile, id: &Id, shortcuts: Widget, theme: Theme) -> Self { + let repo = profile.storage.repository(*id).unwrap(); + let patches = Patches::open(&repo) + .and_then(|patches| patches.all().map(|iter| iter.flatten().collect::>())); + + let header = [ + common::label(" ● "), + common::label("ID"), + common::label("Title"), + common::label("Author"), + common::label("Head"), + common::label("+"), + common::label("-"), + common::label("Updated"), + ]; + + let widths = [ + ColumnWidth::Fixed(3), + ColumnWidth::Fixed(7), + ColumnWidth::Grow, + ColumnWidth::Fixed(21), + ColumnWidth::Fixed(7), + ColumnWidth::Fixed(4), + ColumnWidth::Fixed(4), + ColumnWidth::Fixed(18), + ]; + + let mut items = vec![]; + if let Ok(mut patches) = patches { + patches.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp())); + patches.sort_by(|(_, a, _), (_, b, _)| a.state().cmp(b.state())); + + for (id, patch, _) in patches { + if let Ok(item) = PatchItem::try_from((profile, &repo, id, patch)) { + items.push(item); + } + } + } + + let table = Widget::new(Table::new(&items, header, widths, theme.clone())) + .highlight(theme.colors.item_list_highlighted_bg); + + Self { table, shortcuts } + } + + pub fn selected_item(&self) -> Option<&PatchItem> { + self.table.selection() + } +} + +impl WidgetComponent for PatchBrowser { + fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) { + let shortcuts_h = self + .shortcuts + .query(Attribute::Height) + .unwrap_or(AttrValue::Size(0)) + .unwrap_size(); + let layout = layout::root_component(area, shortcuts_h); + + self.table.view(frame, layout[0]); + self.shortcuts.view(frame, layout[1]); + } + + fn state(&self) -> State { + self.table.state() + } + + fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult { + self.table.perform(cmd) + } +} + pub fn navigation(theme: &Theme) -> Widget { common::tabs( theme, @@ -125,11 +207,7 @@ pub fn dashboard(theme: &Theme, id: &Id, project: &Project) -> Widget Widget::new(dashboard) } -pub fn patches( - theme: &Theme, - items: &[(PatchId, Patch)], - profile: &Profile, -) -> Widget> { +pub fn patches(theme: &Theme, id: &Id, profile: &Profile) -> Widget { let shortcuts = common::shortcuts( theme, vec![ @@ -140,13 +218,7 @@ pub fn patches( ], ); - let labels = vec!["", "title", "author", "time", "comments", "tags"]; - let widths = vec![3u16, 42, 15, 10, 5, 25]; - - let table = common::table(theme, &labels, &widths, items, profile); - let browser: Browser<(PatchId, Patch)> = Browser::new(table, shortcuts); - - Widget::new(browser) + Widget::new(PatchBrowser::new(profile, id, shortcuts, theme.clone())) } pub fn issues(theme: &Theme) -> Widget { diff --git a/radicle-tui/src/ui/widget/patch.rs b/radicle-tui/src/ui/widget/patch.rs index 67527fa9..7a8a5434 100644 --- a/radicle-tui/src/ui/widget/patch.rs +++ b/radicle-tui/src/ui/widget/patch.rs @@ -179,8 +179,8 @@ pub fn context(theme: &Theme, patch: (PatchId, &Patch), profile: &Profile) -> Wi let id = format::cob(&id); let title = patch.title(); - let author = cob::format_author(patch, profile); - let comments = rev.discussion().comments().count(); + let author = cob::format_author(patch.author().id(), is_you); + let comments = rev.discussion().len(); let context = common::label(" patch ").background(theme.colors.context_badge_bg); let id = common::label(&format!(" {id} "))