tui: Add detail widget to issue page
This commit is contained in:
parent
3b88e13e91
commit
df5cf4023b
|
|
@ -39,6 +39,7 @@ pub enum PatchCid {
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum IssueCid {
|
pub enum IssueCid {
|
||||||
List,
|
List,
|
||||||
|
Details,
|
||||||
Shortcuts,
|
Shortcuts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@ pub enum HomeMessage {}
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum IssueMessage {
|
pub enum IssueMessage {
|
||||||
Show(IssueId),
|
Show(IssueId),
|
||||||
|
Changed(IssueId),
|
||||||
Leave,
|
Leave,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +188,9 @@ impl Tui<Cid, Message> for App {
|
||||||
}
|
}
|
||||||
Message::Quit => self.quit = true,
|
Message::Quit => self.quit = true,
|
||||||
_ => {
|
_ => {
|
||||||
self.pages.peek_mut()?.update(app, message)?;
|
self.pages
|
||||||
|
.peek_mut()?
|
||||||
|
.update(app, &self.context, &theme, message)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,20 +52,38 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::LargeList> {
|
||||||
Some(Message::Issue(IssueMessage::Leave))
|
Some(Message::Issue(IssueMessage::Leave))
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||||
self.perform(Cmd::Move(MoveDirection::Up));
|
let result = self.perform(Cmd::Move(MoveDirection::Up));
|
||||||
Some(Message::Tick)
|
match result {
|
||||||
|
CmdResult::Changed(State::One(StateValue::Usize(selected))) => {
|
||||||
|
let item = self.items().get(selected)?;
|
||||||
|
Some(Message::Issue(IssueMessage::Changed(item.id().to_owned())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Down, ..
|
code: Key::Down, ..
|
||||||
}) => {
|
}) => {
|
||||||
self.perform(Cmd::Move(MoveDirection::Down));
|
let result = self.perform(Cmd::Move(MoveDirection::Down));
|
||||||
Some(Message::Tick)
|
match result {
|
||||||
|
CmdResult::Changed(State::One(StateValue::Usize(selected))) => {
|
||||||
|
let item = self.items().get(selected)?;
|
||||||
|
Some(Message::Issue(IssueMessage::Changed(item.id().to_owned())))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<issue::Details> {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchBrowser> {
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchBrowser> {
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
match event {
|
match event {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use anyhow::Result;
|
||||||
use radicle::cob::issue::{Issue, IssueId};
|
use radicle::cob::issue::{Issue, IssueId};
|
||||||
use radicle::cob::patch::{Patch, PatchId};
|
use radicle::cob::patch::{Patch, PatchId};
|
||||||
|
|
||||||
|
use radicle_tui::cob;
|
||||||
use tuirealm::{Frame, NoUserEvent, Sub, SubClause};
|
use tuirealm::{Frame, NoUserEvent, Sub, SubClause};
|
||||||
|
|
||||||
use radicle_tui::ui::context::Context;
|
use radicle_tui::ui::context::Context;
|
||||||
|
|
@ -10,7 +11,7 @@ use radicle_tui::ui::layout;
|
||||||
use radicle_tui::ui::theme::Theme;
|
use radicle_tui::ui::theme::Theme;
|
||||||
use radicle_tui::ui::widget;
|
use radicle_tui::ui::widget;
|
||||||
|
|
||||||
use super::{subscription, Application, Cid, HomeCid, IssueCid, Message, PatchCid};
|
use super::{subscription, Application, Cid, HomeCid, IssueCid, IssueMessage, Message, PatchCid};
|
||||||
|
|
||||||
/// `tuirealm`'s event and prop system is designed to work with flat component hierarchies.
|
/// `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
|
/// Building deep nested component hierarchies would need a lot more additional effort to
|
||||||
|
|
@ -36,6 +37,8 @@ pub trait ViewPage {
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
message: Message,
|
message: Message,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
|
|
@ -101,6 +104,8 @@ impl ViewPage for HomeView {
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
_context: &Context,
|
||||||
|
_theme: &Theme,
|
||||||
message: Message,
|
message: Message,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Message::NavigationChanged(index) = message {
|
if let Message::NavigationChanged(index) = message {
|
||||||
|
|
@ -162,8 +167,9 @@ impl ViewPage for IssuePage {
|
||||||
context: &Context,
|
context: &Context,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let (id, issue) = self.issue.clone();
|
let (id, issue) = &self.issue;
|
||||||
let list = widget::issue::list(context, theme, (id, issue)).to_boxed();
|
let list = widget::issue::list(context, theme, (*id, issue.clone())).to_boxed();
|
||||||
|
let details = widget::issue::details(context, theme, (*id, issue.clone())).to_boxed();
|
||||||
let shortcuts = widget::common::shortcuts(
|
let shortcuts = widget::common::shortcuts(
|
||||||
theme,
|
theme,
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -174,6 +180,7 @@ impl ViewPage for IssuePage {
|
||||||
.to_boxed();
|
.to_boxed();
|
||||||
|
|
||||||
app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
|
app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
|
||||||
|
app.remount(Cid::Issue(IssueCid::Details), details, vec![])?;
|
||||||
app.remount(Cid::Issue(IssueCid::Shortcuts), shortcuts, vec![])?;
|
app.remount(Cid::Issue(IssueCid::Shortcuts), shortcuts, vec![])?;
|
||||||
|
|
||||||
app.active(&self.active_component)?;
|
app.active(&self.active_component)?;
|
||||||
|
|
@ -183,6 +190,7 @@ impl ViewPage for IssuePage {
|
||||||
|
|
||||||
fn unmount(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
fn unmount(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||||
app.umount(&Cid::Issue(IssueCid::List))?;
|
app.umount(&Cid::Issue(IssueCid::List))?;
|
||||||
|
app.umount(&Cid::Issue(IssueCid::Details))?;
|
||||||
app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
|
app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -190,8 +198,17 @@ impl ViewPage for IssuePage {
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
_message: Message,
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
|
message: Message,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
if let Message::Issue(IssueMessage::Changed(id)) = message {
|
||||||
|
let repo = context.repository();
|
||||||
|
if let Some(issue) = cob::issue::find(repo, &id)? {
|
||||||
|
let details = widget::issue::details(context, theme, (id, issue)).to_boxed();
|
||||||
|
app.remount(Cid::Issue(IssueCid::Details), details, vec![])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
app.active(&self.active_component)?;
|
app.active(&self.active_component)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -203,6 +220,7 @@ impl ViewPage for IssuePage {
|
||||||
let layout = layout::issue_preview(area, shortcuts_h);
|
let layout = layout::issue_preview(area, shortcuts_h);
|
||||||
|
|
||||||
app.view(&Cid::Issue(IssueCid::List), frame, layout.left);
|
app.view(&Cid::Issue(IssueCid::List), frame, layout.left);
|
||||||
|
app.view(&Cid::Issue(IssueCid::Details), frame, layout.details);
|
||||||
app.view(&Cid::Issue(IssueCid::Shortcuts), frame, layout.shortcuts);
|
app.view(&Cid::Issue(IssueCid::Shortcuts), frame, layout.shortcuts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,6 +281,8 @@ impl ViewPage for PatchView {
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
_context: &Context,
|
||||||
|
_theme: &Theme,
|
||||||
message: Message,
|
message: Message,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Message::NavigationChanged(index) = message {
|
if let Message::NavigationChanged(index) = message {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,16 @@ pub struct AuthorItem {
|
||||||
is_you: bool,
|
is_you: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AuthorItem {
|
||||||
|
pub fn did(&self) -> Did {
|
||||||
|
self.did
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_you(&self) -> bool {
|
||||||
|
self.is_you
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A patch item that can be used in tables, list or trees.
|
/// A patch item that can be used in tables, list or trees.
|
||||||
///
|
///
|
||||||
/// Breaks up dependencies to [`Profile`] and [`Repository`] that
|
/// Breaks up dependencies to [`Profile`] and [`Repository`] that
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ use tuirealm::MockComponent;
|
||||||
|
|
||||||
pub struct IssuePreview {
|
pub struct IssuePreview {
|
||||||
pub left: Rect,
|
pub left: Rect,
|
||||||
pub right: Rect,
|
pub details: Rect,
|
||||||
|
pub discussion: Rect,
|
||||||
pub shortcuts: Rect,
|
pub shortcuts: Rect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,9 +159,15 @@ pub fn issue_preview(area: Rect, shortcuts_h: u16) -> IssuePreview {
|
||||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
.split(root[0]);
|
.split(root[0]);
|
||||||
|
|
||||||
|
let right = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(6), Constraint::Min(0)].as_ref())
|
||||||
|
.split(split[1]);
|
||||||
|
|
||||||
IssuePreview {
|
IssuePreview {
|
||||||
left: split[0],
|
left: split[0],
|
||||||
right: split[1],
|
details: right[0],
|
||||||
|
discussion: right[1],
|
||||||
shortcuts: root[1],
|
shortcuts: root[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ use context::{Shortcut, Shortcuts};
|
||||||
use label::Label;
|
use label::Label;
|
||||||
use list::{Property, PropertyList};
|
use list::{Property, PropertyList};
|
||||||
|
|
||||||
use self::list::ColumnWidth;
|
use self::container::Container;
|
||||||
|
use self::list::{ColumnWidth, PropertyTable};
|
||||||
|
|
||||||
use super::Widget;
|
use super::Widget;
|
||||||
|
|
||||||
|
|
@ -43,6 +44,11 @@ pub fn container_header(theme: &Theme, label: Widget<Label>) -> Widget<Header<1>
|
||||||
Widget::new(header)
|
Widget::new(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn container(_theme: &Theme, component: Box<dyn MockComponent>) -> Widget<Container> {
|
||||||
|
let container = Container::new(component);
|
||||||
|
Widget::new(container)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn labeled_container(
|
pub fn labeled_container(
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
title: &str,
|
title: &str,
|
||||||
|
|
@ -92,7 +98,7 @@ pub fn property(theme: &Theme, name: &str, value: &str) -> Widget<Property> {
|
||||||
let value_w = value.query(Attribute::Width).unwrap().unwrap_size();
|
let value_w = value.query(Attribute::Width).unwrap().unwrap_size();
|
||||||
let width = name_w.saturating_add(divider_w).saturating_add(value_w);
|
let width = name_w.saturating_add(divider_w).saturating_add(value_w);
|
||||||
|
|
||||||
let property = Property::new(name, divider, value);
|
let property = Property::new(name, value).with_divider(divider);
|
||||||
|
|
||||||
Widget::new(property).height(1).width(width)
|
Widget::new(property).height(1).width(width)
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +109,12 @@ pub fn property_list(_theme: &Theme, properties: Vec<Widget<Property>>) -> Widge
|
||||||
Widget::new(property_list)
|
Widget::new(property_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn property_table(_theme: &Theme, properties: Vec<Widget<Property>>) -> Widget<PropertyTable> {
|
||||||
|
let table = PropertyTable::new(properties);
|
||||||
|
|
||||||
|
Widget::new(table)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tabs(theme: &Theme, tabs: Vec<Widget<Label>>) -> Widget<Tabs> {
|
pub fn tabs(theme: &Theme, tabs: Vec<Widget<Label>>) -> Widget<Tabs> {
|
||||||
let line =
|
let line =
|
||||||
label(&theme.icons.tab_overline.to_string()).foreground(theme.colors.tabs_highlighted_fg);
|
label(&theme.icons.tab_overline.to_string()).foreground(theme.colors.tabs_highlighted_fg);
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,50 @@ impl<const W: usize> WidgetComponent for Header<W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Container {
|
||||||
|
component: Box<dyn MockComponent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
pub fn new(component: Box<dyn MockComponent>) -> Self {
|
||||||
|
Self { component }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetComponent for Container {
|
||||||
|
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
||||||
|
let display = properties
|
||||||
|
.get_or(Attribute::Display, AttrValue::Flag(true))
|
||||||
|
.unwrap_flag();
|
||||||
|
|
||||||
|
if display {
|
||||||
|
// Make some space on the left
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.horizontal_margin(1)
|
||||||
|
.vertical_margin(1)
|
||||||
|
.constraints(vec![Constraint::Length(1), Constraint::Min(0)].as_ref())
|
||||||
|
.split(area);
|
||||||
|
// reverse draw order: child needs to be drawn first?
|
||||||
|
self.component.view(frame, layout[1]);
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(BorderSides::ALL)
|
||||||
|
.border_style(Style::default().fg(Color::Rgb(48, 48, 48)))
|
||||||
|
.border_type(BorderType::Rounded);
|
||||||
|
frame.render_widget(block, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> State {
|
||||||
|
State::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||||
|
self.component.perform(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct LabeledContainer {
|
pub struct LabeledContainer {
|
||||||
header: Widget<Header<1>>,
|
header: Widget<Header<1>>,
|
||||||
component: Box<dyn MockComponent>,
|
component: Box<dyn MockComponent>,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::ui::widget::{utils, Widget, WidgetComponent};
|
||||||
|
|
||||||
use super::container::Header;
|
use super::container::Header;
|
||||||
use super::label::Label;
|
use super::label::Label;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
/// A generic item that can be displayed in a table with [`W`] columns.
|
/// A generic item that can be displayed in a table with [`W`] columns.
|
||||||
pub trait TableItem<const W: usize> {
|
pub trait TableItem<const W: usize> {
|
||||||
|
|
@ -40,19 +41,33 @@ pub enum ColumnWidth {
|
||||||
/// A component that displays a labeled property.
|
/// A component that displays a labeled property.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Property {
|
pub struct Property {
|
||||||
label: Widget<Label>,
|
name: Widget<Label>,
|
||||||
divider: Widget<Label>,
|
divider: Widget<Label>,
|
||||||
property: Widget<Label>,
|
value: Widget<Label>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Property {
|
impl Property {
|
||||||
pub fn new(label: Widget<Label>, divider: Widget<Label>, property: Widget<Label>) -> Self {
|
pub fn new(name: Widget<Label>, value: Widget<Label>) -> Self {
|
||||||
|
let divider = label("");
|
||||||
Self {
|
Self {
|
||||||
label,
|
name,
|
||||||
divider,
|
divider,
|
||||||
property,
|
value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_divider(mut self, divider: Widget<Label>) -> Self {
|
||||||
|
self.divider = divider;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &Widget<Label> {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> &Widget<Label> {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetComponent for Property {
|
impl WidgetComponent for Property {
|
||||||
|
|
@ -63,9 +78,9 @@ impl WidgetComponent for Property {
|
||||||
|
|
||||||
if display {
|
if display {
|
||||||
let labels: Vec<Box<dyn MockComponent>> = vec![
|
let labels: Vec<Box<dyn MockComponent>> = vec![
|
||||||
self.label.clone().to_boxed(),
|
self.name.clone().to_boxed(),
|
||||||
self.divider.clone().to_boxed(),
|
self.divider.clone().to_boxed(),
|
||||||
self.property.clone().to_boxed(),
|
self.value.clone().to_boxed(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let layout = layout::h_stack(labels, area);
|
let layout = layout::h_stack(labels, area);
|
||||||
|
|
@ -125,6 +140,45 @@ impl WidgetComponent for PropertyList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct PropertyTable {
|
||||||
|
properties: Vec<Widget<Property>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PropertyTable {
|
||||||
|
pub fn new(properties: Vec<Widget<Property>>) -> Self {
|
||||||
|
Self { properties }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetComponent for PropertyTable {
|
||||||
|
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
|
||||||
|
use tuirealm::tui::widgets::Table;
|
||||||
|
|
||||||
|
let display = properties
|
||||||
|
.get_or(Attribute::Display, AttrValue::Flag(true))
|
||||||
|
.unwrap_flag();
|
||||||
|
|
||||||
|
if display {
|
||||||
|
let rows = self
|
||||||
|
.properties
|
||||||
|
.iter()
|
||||||
|
.map(|p| Row::new([Cell::from(p.name()), Cell::from(p.value())]));
|
||||||
|
|
||||||
|
let table = Table::new(rows)
|
||||||
|
.widths([Constraint::Percentage(20), Constraint::Percentage(80)].as_ref());
|
||||||
|
frame.render_widget(table, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> State {
|
||||||
|
State::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&mut self, _properties: &Props, _cmd: Cmd) -> CmdResult {
|
||||||
|
CmdResult::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A table component that can display a list of [`TableItem`]s hold by a [`TableModel`].
|
/// A table component that can display a list of [`TableItem`]s hold by a [`TableModel`].
|
||||||
pub struct Table<V, const W: usize>
|
pub struct Table<V, const W: usize>
|
||||||
where
|
where
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ use radicle::cob::issue::IssueId;
|
||||||
use radicle::Profile;
|
use radicle::Profile;
|
||||||
use tuirealm::props::Color;
|
use tuirealm::props::Color;
|
||||||
|
|
||||||
|
use super::common::container::Container;
|
||||||
use super::common::container::LabeledContainer;
|
use super::common::container::LabeledContainer;
|
||||||
use super::common::list::List;
|
use super::common::list::List;
|
||||||
|
use super::common::list::Property;
|
||||||
use super::Widget;
|
use super::Widget;
|
||||||
|
|
||||||
use crate::cob;
|
use crate::ui::cob;
|
||||||
use crate::ui::cob::IssueItem;
|
use crate::ui::cob::IssueItem;
|
||||||
use crate::ui::context::Context;
|
use crate::ui::context::Context;
|
||||||
use crate::ui::theme::Theme;
|
use crate::ui::theme::Theme;
|
||||||
|
|
@ -18,13 +20,14 @@ use crate::ui::widget::common::context::ContextBar;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub struct LargeList {
|
pub struct LargeList {
|
||||||
container: Widget<LabeledContainer>,
|
items: Vec<IssueItem>,
|
||||||
|
list: Widget<LabeledContainer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LargeList {
|
impl LargeList {
|
||||||
pub fn new(context: &Context, theme: &Theme, selected: Option<(IssueId, Issue)>) -> Self {
|
pub fn new(context: &Context, theme: &Theme, selected: Option<(IssueId, Issue)>) -> Self {
|
||||||
let repo = context.repository();
|
let repo = context.repository();
|
||||||
let issues = cob::issue::all(repo).unwrap_or_default();
|
let issues = crate::cob::issue::all(repo).unwrap_or_default();
|
||||||
let mut items = issues
|
let mut items = issues
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, issue)| IssueItem::from((context.profile(), repo, *id, issue.clone())))
|
.map(|(id, issue)| IssueItem::from((context.profile(), repo, *id, issue.clone())))
|
||||||
|
|
@ -41,11 +44,87 @@ impl LargeList {
|
||||||
|
|
||||||
let container = common::labeled_container(theme, "Issues", list.to_boxed());
|
let container = common::labeled_container(theme, "Issues", list.to_boxed());
|
||||||
|
|
||||||
Self { container }
|
Self {
|
||||||
|
items,
|
||||||
|
list: container,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn items(&self) -> &Vec<IssueItem> {
|
||||||
|
&self.items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetComponent for LargeList {
|
impl WidgetComponent for LargeList {
|
||||||
|
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
|
||||||
|
self.list.view(frame, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> State {
|
||||||
|
State::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
||||||
|
self.list.perform(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Details {
|
||||||
|
container: Widget<Container>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Details {
|
||||||
|
pub fn new(context: &Context, theme: &Theme, issue: (IssueId, Issue)) -> Self {
|
||||||
|
let repo = context.repository();
|
||||||
|
|
||||||
|
let (id, issue) = issue;
|
||||||
|
let item = IssueItem::from((context.profile(), repo, id, issue));
|
||||||
|
|
||||||
|
let title = Property::new(
|
||||||
|
common::label("Title").foreground(theme.colors.property_name_fg),
|
||||||
|
common::label(item.title()).foreground(theme.colors.browser_list_title),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tags = Property::new(
|
||||||
|
common::label("Tags").foreground(theme.colors.property_name_fg),
|
||||||
|
common::label(&cob::format_tags(item.tags()))
|
||||||
|
.foreground(theme.colors.browser_list_tags),
|
||||||
|
);
|
||||||
|
|
||||||
|
let assignees = Property::new(
|
||||||
|
common::label("Assignees").foreground(theme.colors.property_name_fg),
|
||||||
|
common::label(&cob::format_assignees(
|
||||||
|
&item
|
||||||
|
.assignees()
|
||||||
|
.iter()
|
||||||
|
.map(|item| (item.did(), item.is_you()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
))
|
||||||
|
.foreground(theme.colors.browser_list_author),
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = Property::new(
|
||||||
|
common::label("Status").foreground(theme.colors.property_name_fg),
|
||||||
|
common::label(&item.state().to_string()).foreground(theme.colors.browser_list_title),
|
||||||
|
);
|
||||||
|
|
||||||
|
// let table = common::property_table(theme, vec![title, tags, assignees, state]);
|
||||||
|
let table = common::property_table(
|
||||||
|
theme,
|
||||||
|
vec![
|
||||||
|
Widget::new(title),
|
||||||
|
Widget::new(tags),
|
||||||
|
Widget::new(assignees),
|
||||||
|
Widget::new(state),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let container = common::container(theme, table.to_boxed());
|
||||||
|
|
||||||
|
Self { container }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetComponent for Details {
|
||||||
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
|
fn view(&mut self, _properties: &Props, frame: &mut Frame, area: Rect) {
|
||||||
self.container.view(frame, area);
|
self.container.view(frame, area);
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +133,8 @@ impl WidgetComponent for LargeList {
|
||||||
State::None
|
State::None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
|
fn perform(&mut self, _properties: &Props, _cmd: Cmd) -> CmdResult {
|
||||||
self.container.perform(cmd)
|
CmdResult::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,9 +144,12 @@ pub fn list(context: &Context, theme: &Theme, issue: (IssueId, Issue)) -> Widget
|
||||||
Widget::new(list)
|
Widget::new(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<ContextBar> {
|
pub fn details(context: &Context, theme: &Theme, issue: (IssueId, Issue)) -> Widget<Details> {
|
||||||
use crate::ui::cob;
|
let details = Details::new(context, theme, issue);
|
||||||
|
Widget::new(details)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<ContextBar> {
|
||||||
let (id, issue) = issue;
|
let (id, issue) = issue;
|
||||||
let is_you = *issue.author().id() == profile.did();
|
let is_you = *issue.author().id() == profile.did();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue