tui: Implement larger list on issue page
This commit is contained in:
parent
8e7d53aa9a
commit
dd16ed780e
|
|
@ -39,6 +39,7 @@ pub enum PatchCid {
|
|||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub enum IssueCid {
|
||||
List,
|
||||
Shortcuts,
|
||||
}
|
||||
|
||||
/// All component ids known to this application.
|
||||
|
|
@ -174,6 +175,9 @@ impl Tui<Cid, Message> for App {
|
|||
Message::Issue(IssueMessage::Show(id)) => {
|
||||
self.view_issue(app, id, &theme)?;
|
||||
}
|
||||
Message::Issue(IssueMessage::Leave) => {
|
||||
self.pages.pop(app)?;
|
||||
}
|
||||
Message::Patch(PatchMessage::Show(id)) => {
|
||||
self.view_patch(app, id, &theme)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use radicle_tui::ui::widget::common::container::{GlobalListener, LabeledContaine
|
|||
use radicle_tui::ui::widget::common::context::{ContextBar, Shortcuts};
|
||||
use radicle_tui::ui::widget::common::list::PropertyList;
|
||||
use radicle_tui::ui::widget::home::{Dashboard, IssueBrowser, PatchBrowser};
|
||||
use radicle_tui::ui::widget::patch;
|
||||
use radicle_tui::ui::widget::{issue, patch};
|
||||
|
||||
use radicle_tui::ui::widget::Widget;
|
||||
|
||||
|
|
@ -45,6 +45,27 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<Tabs> {
|
|||
}
|
||||
}
|
||||
|
||||
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::Up, .. }) => {
|
||||
self.perform(Cmd::Move(MoveDirection::Up));
|
||||
Some(Message::Tick)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
}) => {
|
||||
self.perform(Cmd::Move(MoveDirection::Down));
|
||||
Some(Message::Tick)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchBrowser> {
|
||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||
match event {
|
||||
|
|
|
|||
|
|
@ -163,9 +163,19 @@ impl ViewPage for IssuePage {
|
|||
theme: &Theme,
|
||||
) -> Result<()> {
|
||||
let (id, issue) = &self.issue;
|
||||
let list = widget::issue::list(theme, (*id, issue), context.profile()).to_boxed();
|
||||
let list = widget::issue::list(context, theme, (*id, issue)).to_boxed();
|
||||
let shortcuts = widget::common::shortcuts(
|
||||
theme,
|
||||
vec![
|
||||
widget::common::shortcut(theme, "esc", "back"),
|
||||
widget::common::shortcut(theme, "q", "quit"),
|
||||
],
|
||||
)
|
||||
.to_boxed();
|
||||
|
||||
app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
|
||||
app.remount(Cid::Issue(IssueCid::Shortcuts), shortcuts, vec![])?;
|
||||
|
||||
app.active(&self.active_component)?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -173,6 +183,7 @@ impl ViewPage for IssuePage {
|
|||
|
||||
fn unmount(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||
app.umount(&Cid::Issue(IssueCid::List))?;
|
||||
app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -188,27 +199,18 @@ impl ViewPage for IssuePage {
|
|||
|
||||
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
||||
let area = frame.size();
|
||||
let layout = layout::default_page(area);
|
||||
let shortcuts_h = 1u16;
|
||||
let layout = layout::issue_preview(area, shortcuts_h);
|
||||
|
||||
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
|
||||
app.view(&self.active_component, frame, layout[1]);
|
||||
app.view(&Cid::Issue(IssueCid::List), frame, layout.left);
|
||||
app.view(&Cid::Issue(IssueCid::Shortcuts), frame, layout.shortcuts);
|
||||
}
|
||||
|
||||
fn subscribe(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||
app.subscribe(
|
||||
&Cid::Home(HomeCid::Navigation),
|
||||
Sub::new(subscription::navigation_clause(), SubClause::Always),
|
||||
)?;
|
||||
|
||||
fn subscribe(&self, _app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unsubscribe(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||
app.unsubscribe(
|
||||
&Cid::Home(HomeCid::Navigation),
|
||||
subscription::navigation_clause(),
|
||||
)?;
|
||||
|
||||
fn unsubscribe(&self, _app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ use radicle::cob::patch::{Patch, PatchId, State as PatchState};
|
|||
use radicle::cob::{Tag, Timestamp};
|
||||
|
||||
use tuirealm::props::{Color, Style};
|
||||
use tuirealm::tui::text::{Span, Spans};
|
||||
use tuirealm::tui::widgets::Cell;
|
||||
|
||||
use crate::ui::theme::Theme;
|
||||
use crate::ui::widget::common::list::TableItem;
|
||||
|
||||
use super::widget::common::list::ListItem;
|
||||
|
||||
/// An author item that can be used in tables, list or trees.
|
||||
///
|
||||
/// Breaks up dependencies to [`Profile`] and [`Repository`] that
|
||||
|
|
@ -197,13 +200,11 @@ impl IssueItem {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: (&Profile, &Repository, IssueId, Issue)) -> Result<Self, Self::Error> {
|
||||
impl From<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
|
||||
fn from(value: (&Profile, &Repository, IssueId, Issue)) -> Self {
|
||||
let (profile, _, id, issue) = value;
|
||||
|
||||
Ok(IssueItem {
|
||||
IssueItem {
|
||||
id,
|
||||
state: *issue.state(),
|
||||
title: issue.title().into(),
|
||||
|
|
@ -220,7 +221,7 @@ impl TryFrom<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
|
|||
})
|
||||
.collect::<Vec<_>>(),
|
||||
timestamp: issue.timestamp(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +257,37 @@ impl TableItem<7> for IssueItem {
|
|||
}
|
||||
}
|
||||
|
||||
impl ListItem for IssueItem {
|
||||
fn row(&self, theme: &Theme) -> tuirealm::tui::widgets::ListItem {
|
||||
let (state, state_color) = format_issue_state(&self.state);
|
||||
let lines = vec![
|
||||
Spans::from(vec![
|
||||
Span::styled(state, Style::default().fg(state_color)),
|
||||
Span::styled(
|
||||
self.title.clone(),
|
||||
Style::default().fg(theme.colors.browser_list_title),
|
||||
),
|
||||
]),
|
||||
Spans::from(vec![
|
||||
Span::raw(String::from(" ")),
|
||||
Span::styled(
|
||||
format_author(&self.author.did, self.author.is_you),
|
||||
Style::default().fg(theme.colors.browser_list_author),
|
||||
),
|
||||
Span::styled(
|
||||
format!(" {} ", theme.icons.property_divider),
|
||||
Style::default().fg(theme.colors.property_divider_fg),
|
||||
),
|
||||
Span::styled(
|
||||
format::timestamp(&self.timestamp).to_string(),
|
||||
Style::default().fg(theme.colors.browser_list_timestamp),
|
||||
),
|
||||
]),
|
||||
];
|
||||
tuirealm::tui::widgets::ListItem::new(lines)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_patch_state(state: &PatchState) -> (String, Color) {
|
||||
match state {
|
||||
PatchState::Open { conflicts: _ } => (" ● ".into(), Color::Green),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ use tuirealm::props::{AttrValue, Attribute};
|
|||
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use tuirealm::MockComponent;
|
||||
|
||||
pub struct IssuePreview {
|
||||
pub left: Rect,
|
||||
pub right: Rect,
|
||||
pub shortcuts: Rect,
|
||||
}
|
||||
|
||||
pub fn v_stack(
|
||||
widgets: Vec<Box<dyn MockComponent>>,
|
||||
area: Rect,
|
||||
|
|
@ -58,6 +64,17 @@ pub fn default_page(area: Rect) -> Vec<Rect> {
|
|||
.split(area)
|
||||
}
|
||||
|
||||
pub fn headerless_page(area: Rect) -> Vec<Rect> {
|
||||
let margin_h = 1u16;
|
||||
let content_h = area.height.saturating_sub(margin_h);
|
||||
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.horizontal_margin(margin_h)
|
||||
.constraints([Constraint::Length(content_h)].as_ref())
|
||||
.split(area)
|
||||
}
|
||||
|
||||
pub fn root_component(area: Rect, shortcuts_h: u16) -> Vec<Rect> {
|
||||
let content_h = area.height.saturating_sub(shortcuts_h);
|
||||
|
||||
|
|
@ -120,3 +137,30 @@ pub fn centered_label(label_w: u16, area: Rect) -> Rect {
|
|||
)
|
||||
.split(layout[1])[1]
|
||||
}
|
||||
|
||||
pub fn issue_preview(area: Rect, shortcuts_h: u16) -> IssuePreview {
|
||||
let content_h = area.height.saturating_sub(shortcuts_h);
|
||||
|
||||
let root = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.horizontal_margin(1)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Length(content_h),
|
||||
Constraint::Length(shortcuts_h),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(area);
|
||||
|
||||
let split = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(root[0]);
|
||||
|
||||
IssuePreview {
|
||||
left: split[0],
|
||||
right: split[1],
|
||||
shortcuts: root[1],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use tuirealm::tui::widgets::{ListState, TableState};
|
||||
|
||||
/// State that holds the index of a selected tab item and the count of all tab items.
|
||||
/// The index can be increased and will start at 0, if length was reached.
|
||||
#[derive(Clone, Default)]
|
||||
|
|
@ -15,3 +17,72 @@ impl TabState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ItemState {
|
||||
selected: Option<usize>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl ItemState {
|
||||
pub fn new(len: usize) -> Self {
|
||||
Self {
|
||||
selected: Some(0),
|
||||
len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Option<usize> {
|
||||
self.selected
|
||||
}
|
||||
|
||||
pub fn select_previous(&mut self) -> Option<usize> {
|
||||
let old_index = self.selected();
|
||||
let new_index = match old_index {
|
||||
Some(selected) if selected == 0 => Some(0),
|
||||
Some(selected) => Some(selected.saturating_sub(1)),
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
if old_index != new_index {
|
||||
self.selected = new_index;
|
||||
self.selected()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self) -> Option<usize> {
|
||||
let old_index = self.selected();
|
||||
let new_index = match old_index {
|
||||
Some(selected) if selected >= self.len.saturating_sub(1) => {
|
||||
Some(self.len.saturating_sub(1))
|
||||
}
|
||||
Some(selected) => Some(selected.saturating_add(1)),
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
if old_index != new_index {
|
||||
self.selected = new_index;
|
||||
self.selected()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ItemState> for TableState {
|
||||
fn from(value: &ItemState) -> Self {
|
||||
let mut state = TableState::default();
|
||||
state.select(value.selected);
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ItemState> for ListState {
|
||||
fn from(value: &ItemState) -> Self {
|
||||
let mut state = ListState::default();
|
||||
state.select(value.selected);
|
||||
state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub struct Colors {
|
|||
pub shortcutbar_divider_fg: Color,
|
||||
pub browser_list_id: Color,
|
||||
pub browser_list_title: Color,
|
||||
pub browser_list_description: Color,
|
||||
pub browser_list_author: Color,
|
||||
pub browser_list_tags: Color,
|
||||
pub browser_list_comments: Color,
|
||||
|
|
@ -82,12 +83,13 @@ pub fn default_dark() -> Theme {
|
|||
labeled_container_bg: COLOR_DEFAULT_FAINT,
|
||||
item_list_highlighted_bg: COLOR_DEFAULT_DARKER,
|
||||
property_name_fg: Color::Cyan,
|
||||
property_divider_fg: COLOR_DEFAULT_FG,
|
||||
property_divider_fg: COLOR_DEFAULT_DARK,
|
||||
shortcut_short_fg: COLOR_DEFAULT_DARK,
|
||||
shortcut_long_fg: COLOR_DEFAULT_DARKER,
|
||||
shortcutbar_divider_fg: COLOR_DEFAULT_DARKER,
|
||||
browser_list_id: Color::Cyan,
|
||||
browser_list_title: COLOR_DEFAULT_FG,
|
||||
browser_list_description: COLOR_DEFAULT_DARK,
|
||||
browser_list_author: Color::Gray,
|
||||
browser_list_tags: Color::LightBlue,
|
||||
browser_list_comments: COLOR_DEFAULT_DARK_FG,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ pub fn reversable_label(content: &str) -> Widget<Label> {
|
|||
}
|
||||
|
||||
pub fn container_header(theme: &Theme, label: Widget<Label>) -> Widget<Header<1>> {
|
||||
let header = Header::new([label], [ColumnWidth::Fixed(100)], theme.clone());
|
||||
let header = Header::new([label], [ColumnWidth::Grow], theme.clone());
|
||||
|
||||
Widget::new(header)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Color, Props, Style};
|
||||
use tuirealm::tui::layout::{Constraint, Direction, Layout, Rect};
|
||||
use tuirealm::tui::widgets::{Block, Cell, Row, TableState};
|
||||
use tuirealm::tui::widgets::{Block, Cell, ListState, Row, TableState};
|
||||
use tuirealm::{Frame, MockComponent, State, StateValue};
|
||||
|
||||
use crate::ui::layout;
|
||||
use crate::ui::state::ItemState;
|
||||
use crate::ui::theme::Theme;
|
||||
use crate::ui::widget::{utils, Widget, WidgetComponent};
|
||||
|
||||
|
|
@ -17,12 +18,18 @@ pub trait TableItem<const W: usize> {
|
|||
fn row(&self, theme: &Theme) -> [Cell; W];
|
||||
}
|
||||
|
||||
/// A generic item that can be displayed in a list.
|
||||
pub trait ListItem {
|
||||
/// Should return fields as list item.
|
||||
fn row(&self, theme: &Theme) -> tuirealm::tui::widgets::ListItem;
|
||||
}
|
||||
|
||||
/// Grow behavior of a table column.
|
||||
///
|
||||
/// [`tui::widgets::Table`] does only support percental column widths.
|
||||
/// A [`ColumnWidth`] is used to specify the grow behaviour of a table column
|
||||
/// and a percental column width is calculated based on that.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ColumnWidth {
|
||||
/// A fixed-size column.
|
||||
Fixed(u16),
|
||||
|
|
@ -129,7 +136,9 @@ where
|
|||
header: [Widget<Label>; W],
|
||||
/// Grow behavior of table columns.
|
||||
widths: [ColumnWidth; W],
|
||||
state: TableState,
|
||||
/// State that keeps track of the selection.
|
||||
state: ItemState,
|
||||
/// The current theme.
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
|
|
@ -143,49 +152,14 @@ where
|
|||
widths: [ColumnWidth; W],
|
||||
theme: Theme,
|
||||
) -> Self {
|
||||
let mut state = TableState::default();
|
||||
state.select(Some(0));
|
||||
|
||||
Self {
|
||||
items: items.to_vec(),
|
||||
header,
|
||||
widths,
|
||||
state,
|
||||
state: ItemState::new(items.len()),
|
||||
theme,
|
||||
}
|
||||
}
|
||||
|
||||
fn select_previous(&mut self) -> Option<usize> {
|
||||
let old_index = self.state.selected();
|
||||
let new_index = match old_index {
|
||||
Some(selected) if selected == 0 => Some(0),
|
||||
Some(selected) => Some(selected.saturating_sub(1)),
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
if old_index != new_index {
|
||||
self.state.select(new_index);
|
||||
self.state.selected()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn select_next(&mut self, len: usize) -> Option<usize> {
|
||||
let old_index = self.state.selected();
|
||||
let new_index = match old_index {
|
||||
Some(selected) if selected >= len.saturating_sub(1) => Some(len.saturating_sub(1)),
|
||||
Some(selected) => Some(selected.saturating_add(1)),
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
if old_index != new_index {
|
||||
self.state.select(new_index);
|
||||
self.state.selected()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, const W: usize> WidgetComponent for Table<V, W>
|
||||
|
|
@ -226,7 +200,7 @@ where
|
|||
self.theme.clone(),
|
||||
));
|
||||
header.view(frame, layout[0]);
|
||||
frame.render_stateful_widget(table, layout[1], &mut self.state);
|
||||
frame.render_stateful_widget(table, layout[1], &mut TableState::from(&self.state));
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
|
|
@ -236,11 +210,87 @@ where
|
|||
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||
use tuirealm::command::Direction;
|
||||
match cmd {
|
||||
Cmd::Move(Direction::Up) => match self.select_previous() {
|
||||
Cmd::Move(Direction::Up) => match self.state.select_previous() {
|
||||
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||
None => CmdResult::None,
|
||||
},
|
||||
Cmd::Move(Direction::Down) => match self.select_next(self.items.len()) {
|
||||
Cmd::Move(Direction::Down) => match self.state.select_next() {
|
||||
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||
None => CmdResult::None,
|
||||
},
|
||||
Cmd::Submit => match self.state.selected() {
|
||||
Some(selected) => CmdResult::Submit(State::One(StateValue::Usize(selected))),
|
||||
None => CmdResult::None,
|
||||
},
|
||||
_ => CmdResult::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list component that can display [`ListItem`]'s.
|
||||
pub struct List<V>
|
||||
where
|
||||
V: ListItem + Clone,
|
||||
{
|
||||
/// Items held by this list.
|
||||
items: Vec<V>,
|
||||
/// State keeps track of the current selection.
|
||||
state: ItemState,
|
||||
/// The current theme.
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
impl<V> List<V>
|
||||
where
|
||||
V: ListItem + Clone,
|
||||
{
|
||||
pub fn new(items: &[V], theme: Theme) -> Self {
|
||||
Self {
|
||||
items: items.to_vec(),
|
||||
state: ItemState::new(items.len()),
|
||||
theme,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> WidgetComponent for List<V>
|
||||
where
|
||||
V: ListItem + Clone,
|
||||
{
|
||||
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
||||
use tuirealm::tui::widgets::{List, ListItem};
|
||||
|
||||
let highlight = properties
|
||||
.get_or(Attribute::HighlightedColor, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
|
||||
let layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(vec![Constraint::Min(1), Constraint::Length(1)])
|
||||
.split(area);
|
||||
|
||||
let rows: Vec<ListItem> = self
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.row(&self.theme))
|
||||
.collect();
|
||||
let list = List::new(rows).highlight_style(Style::default().bg(highlight));
|
||||
|
||||
frame.render_stateful_widget(list, layout[0], &mut ListState::from(&self.state));
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::None
|
||||
}
|
||||
|
||||
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||
use tuirealm::command::Direction;
|
||||
match cmd {
|
||||
Cmd::Move(Direction::Up) => match self.state.select_previous() {
|
||||
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||
None => CmdResult::None,
|
||||
},
|
||||
Cmd::Move(Direction::Down) => match self.state.select_next() {
|
||||
Some(selected) => CmdResult::Changed(State::One(StateValue::Usize(selected))),
|
||||
None => CmdResult::None,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,32 +5,66 @@ use radicle::cob::issue::IssueId;
|
|||
use radicle::Profile;
|
||||
use tuirealm::props::Color;
|
||||
|
||||
use super::common;
|
||||
use super::common::container::LabeledContainer;
|
||||
use super::common::list::List;
|
||||
use super::Widget;
|
||||
|
||||
use crate::ui::cob;
|
||||
use crate::cob;
|
||||
use crate::ui::cob::IssueItem;
|
||||
use crate::ui::context::Context;
|
||||
use crate::ui::theme::Theme;
|
||||
use crate::ui::widget::common::context::ContextBar;
|
||||
use crate::ui::widget::patch::Activity;
|
||||
|
||||
pub fn list(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<Activity> {
|
||||
let (id, issue) = issue;
|
||||
let shortcuts = common::shortcuts(
|
||||
theme,
|
||||
vec![
|
||||
common::shortcut(theme, "esc", "back"),
|
||||
common::shortcut(theme, "q", "quit"),
|
||||
],
|
||||
);
|
||||
let context = context(theme, (id, issue), profile);
|
||||
use super::*;
|
||||
|
||||
let not_implemented = common::label("not implemented").foreground(theme.colors.default_fg);
|
||||
let activity = Activity::new(not_implemented, context, shortcuts);
|
||||
pub struct LargeList {
|
||||
container: Widget<LabeledContainer>,
|
||||
}
|
||||
|
||||
Widget::new(activity)
|
||||
impl LargeList {
|
||||
pub fn new(context: &Context, theme: &Theme) -> Self {
|
||||
let repo = context.repository();
|
||||
let issues = cob::issue::all(repo).unwrap_or(vec![]);
|
||||
|
||||
let mut items = issues
|
||||
.iter()
|
||||
.map(|(id, issue)| IssueItem::from((context.profile(), repo, *id, issue.clone())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
items.sort_by(|a, b| b.timestamp().cmp(a.timestamp()));
|
||||
items.sort_by(|a, b| a.state().cmp(b.state()));
|
||||
|
||||
let list = Widget::new(List::new(&items, theme.clone()))
|
||||
.highlight(theme.colors.item_list_highlighted_bg);
|
||||
|
||||
let container = common::labeled_container(theme, "Issues", list.to_boxed());
|
||||
|
||||
Self { container }
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetComponent for LargeList {
|
||||
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
|
||||
self.container.view(frame, area);
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::None
|
||||
}
|
||||
|
||||
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||
self.container.perform(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list(context: &Context, theme: &Theme, _issue: (IssueId, &Issue)) -> Widget<LargeList> {
|
||||
let list = LargeList::new(context, theme);
|
||||
Widget::new(list)
|
||||
}
|
||||
|
||||
pub fn context(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<ContextBar> {
|
||||
use crate::ui::cob;
|
||||
|
||||
let (id, issue) = issue;
|
||||
let is_you = *issue.author().id() == profile.did();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue