use radicle::cob::issue::Issues; use radicle::prelude::{Id, Project}; use radicle::storage::ReadStorage; use radicle::Profile; 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::common; use super::common::container::{LabeledContainer, Tabs}; use super::common::context::Shortcuts; use super::common::list::{ColumnWidth, Table}; use super::{Widget, WidgetComponent}; use crate::ui::cob::{IssueItem, PatchItem}; use crate::ui::layout; use crate::ui::theme::Theme; pub struct Dashboard { about: Widget, shortcuts: Widget, } impl Dashboard { pub fn new(about: Widget, shortcuts: Widget) -> Self { Self { about, shortcuts } } } impl WidgetComponent for Dashboard { 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.about.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 { CmdResult::None } } pub struct IssueBrowser { table: Widget>, shortcuts: Widget, } impl IssueBrowser { pub fn new(theme: &Theme, profile: &Profile, id: &Id, shortcuts: Widget) -> Self { let repo = profile.storage.repository(*id).unwrap(); let issues = Issues::open(&repo) .and_then(|issues| issues.all().map(|iter| iter.flatten().collect::>())); let header = [ common::label(" ● "), common::label("ID"), common::label("Title"), common::label("Author"), common::label("Tags"), common::label("Assignees"), common::label("Opened"), ]; let widths = [ ColumnWidth::Fixed(3), ColumnWidth::Fixed(7), ColumnWidth::Grow, ColumnWidth::Fixed(21), ColumnWidth::Fixed(25), ColumnWidth::Fixed(21), ColumnWidth::Fixed(18), ]; let mut items = vec![]; if let Ok(mut issues) = issues { issues.sort_by(|(_, a, _), (_, b, _)| b.timestamp().cmp(&a.timestamp())); issues.sort_by(|(_, a, _), (_, b, _)| a.state().cmp(b.state())); for (id, patch, _) in issues { if let Ok(item) = IssueItem::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<&IssueItem> { self.table.selection() } } impl WidgetComponent for IssueBrowser { 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 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, vec![ common::reversable_label("dashboard").foreground(theme.colors.tabs_highlighted_fg), common::reversable_label("issues").foreground(theme.colors.tabs_highlighted_fg), common::reversable_label("patches").foreground(theme.colors.tabs_highlighted_fg), ], ) } pub fn dashboard(theme: &Theme, id: &Id, project: &Project) -> Widget { let about = common::labeled_container( theme, "about", common::property_list( theme, vec![ common::property(theme, "id", &id.to_string()), common::property(theme, "name", project.name()), common::property(theme, "description", project.description()), ], ) .to_boxed(), ); let shortcuts = common::shortcuts( theme, vec![ common::shortcut(theme, "tab", "section"), common::shortcut(theme, "q", "quit"), ], ); let dashboard = Dashboard::new(about, shortcuts); Widget::new(dashboard) } pub fn patches(theme: &Theme, id: &Id, profile: &Profile) -> Widget { let shortcuts = common::shortcuts( theme, vec![ common::shortcut(theme, "tab", "section"), common::shortcut(theme, "↑/↓", "navigate"), common::shortcut(theme, "enter", "show"), common::shortcut(theme, "q", "quit"), ], ); Widget::new(PatchBrowser::new(profile, id, shortcuts, theme.clone())) } pub fn issues(theme: &Theme, id: &Id, profile: &Profile) -> Widget { let shortcuts = common::shortcuts( theme, vec![ common::shortcut(theme, "tab", "section"), common::shortcut(theme, "↑/↓", "navigate"), common::shortcut(theme, "enter", "show"), common::shortcut(theme, "q", "quit"), ], ); Widget::new(IssueBrowser::new(theme, profile, id, shortcuts)) }