274 lines
8.1 KiB
Rust
274 lines
8.1 KiB
Rust
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<LabeledContainer>,
|
|
shortcuts: Widget<Shortcuts>,
|
|
}
|
|
|
|
impl Dashboard {
|
|
pub fn new(about: Widget<LabeledContainer>, shortcuts: Widget<Shortcuts>) -> 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<Table<IssueItem, 7>>,
|
|
shortcuts: Widget<Shortcuts>,
|
|
}
|
|
|
|
impl IssueBrowser {
|
|
pub fn new(theme: &Theme, profile: &Profile, id: &Id, shortcuts: Widget<Shortcuts>) -> Self {
|
|
let repo = profile.storage.repository(*id).unwrap();
|
|
let issues = Issues::open(&repo)
|
|
.and_then(|issues| issues.all().map(|iter| iter.flatten().collect::<Vec<_>>()));
|
|
|
|
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<Table<PatchItem, 8>>,
|
|
shortcuts: Widget<Shortcuts>,
|
|
}
|
|
|
|
impl PatchBrowser {
|
|
pub fn new(profile: &Profile, id: &Id, shortcuts: Widget<Shortcuts>, 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::<Vec<_>>()));
|
|
|
|
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<Tabs> {
|
|
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<Dashboard> {
|
|
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<PatchBrowser> {
|
|
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<IssueBrowser> {
|
|
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))
|
|
}
|