diff --git a/bin/ui/widget.rs b/bin/ui/widget.rs new file mode 100644 index 00000000..5a818b49 --- /dev/null +++ b/bin/ui/widget.rs @@ -0,0 +1,91 @@ +use std::marker::PhantomData; + +use ratatui::layout::Constraint; +use ratatui::style::Stylize; +use ratatui::text::{Line, Text}; +use ratatui::widgets::Row; +use ratatui::Frame; + +use radicle_tui as tui; + +use tui::ui::span; +use tui::ui::widget::{RenderProps, View, ViewProps}; + +use super::items::IssueItem; + +#[derive(Clone, Default)] +pub struct IssueDetailsProps { + issue: Option, +} + +impl IssueDetailsProps { + pub fn issue(mut self, issue: Option) -> Self { + self.issue = issue; + self + } +} + +pub struct IssueDetails { + /// Phantom + phantom: PhantomData<(S, M)>, +} + +impl Default for IssueDetails { + fn default() -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl View for IssueDetails { + type State = S; + type Message = M; + + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + let default = IssueDetailsProps::default(); + let props = props + .and_then(|props| props.inner_ref::()) + .unwrap_or(&default); + + if let Some(issue) = props.issue.as_ref() { + let author = match &issue.author.alias { + Some(alias) => { + if issue.author.you { + span::alias(&format!("{} (you)", alias)) + } else { + span::alias(alias) + } + } + None => match &issue.author.human_nid { + Some(nid) => span::alias(nid).dim(), + None => span::blank(), + }, + }; + let did = match &issue.author.human_nid { + Some(nid) => span::alias(nid).dim(), + None => span::blank(), + }; + + let table = ratatui::widgets::Table::new( + [ + Row::new([ + Text::raw("Title").cyan(), + Text::raw(issue.title.clone()).bold(), + ]), + Row::new([ + Text::raw("Issue").cyan(), + Text::raw(issue.id.to_string()).bold(), + ]), + Row::new([ + Text::raw("Author").cyan(), + Line::from([author, " ".into(), did].to_vec()).into(), + ]), + Row::new([Text::raw("Status").cyan(), Text::raw("???").magenta()]), + ], + [Constraint::Length(8), Constraint::Fill(1)], + ); + frame.render_widget(table, render.area); + } + } +} diff --git a/src/ui/widget.rs b/src/ui/widget.rs index 9876ecc5..57c02965 100644 --- a/src/ui/widget.rs +++ b/src/ui/widget.rs @@ -177,7 +177,7 @@ pub trait View { fn update(&mut self, _props: Option<&ViewProps>, _state: &Self::State) {} /// Should render the view using the given `RenderProps`. - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame); + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame); } /// A `View` needs to wrapped into a `Widget` before being able to use with the @@ -243,7 +243,7 @@ impl Widget { } /// Renders the wrapped view. - pub fn render(&self, render: RenderProps, frame: &mut Frame) { + pub fn render(&mut self, render: RenderProps, frame: &mut Frame) { self.view.render(self.props.as_ref(), render.clone(), frame); if let Some(on_render) = self.on_render { diff --git a/src/ui/widget/container.rs b/src/ui/widget/container.rs index 6debc256..a82544e4 100644 --- a/src/ui/widget/container.rs +++ b/src/ui/widget/container.rs @@ -80,7 +80,7 @@ impl<'a: 'static, S, M> View for Header { type Message = M; type State = S; - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = HeaderProps::default(); let props = props .and_then(|props| props.inner_ref::()) @@ -210,7 +210,7 @@ impl<'a: 'static, S, M> View for Footer { type Message = M; type State = S; - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = FooterProps::default(); let props = props .and_then(|props| props.inner_ref::()) @@ -326,7 +326,7 @@ where } } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = ContainerProps::default(); let props = props .and_then(|props| props.inner_ref::()) @@ -362,18 +362,18 @@ where .borders(borders); frame.render_widget(block.clone(), content_area); - if let Some(header) = &self.header { + if let Some(header) = self.header.as_mut() { header.render(RenderProps::from(header_area).focus(render.focus), frame); } - if let Some(content) = &self.content { + if let Some(content) = self.content.as_mut() { content.render( RenderProps::from(block.inner(content_area)).focus(render.focus), frame, ); } - if let Some(footer) = &self.footer { + if let Some(footer) = self.footer.as_mut() { footer.render(RenderProps::from(footer_area).focus(render.focus), frame); } } @@ -472,7 +472,7 @@ where } } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = SplitContainerProps::default(); let props = props .and_then(|props| props.inner_ref::()) @@ -492,7 +492,7 @@ where let [top_area, bottom_area] = Layout::vertical(heights).areas(render.area); - if let Some(top) = self.top.as_ref() { + if let Some(top) = self.top.as_mut() { let block = HeaderBlock::default() .borders(Borders::ALL) .border_style(style::border(render.focus)) @@ -509,7 +509,7 @@ where top.render(RenderProps::from(top_area).focus(render.focus), frame) } - if let Some(bottom) = self.bottom.as_ref() { + if let Some(bottom) = self.bottom.as_mut() { let block = Block::default() .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) .border_style(style::border(render.focus)) @@ -637,7 +637,7 @@ where } } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = SectionGroupProps::default(); let props = props .and_then(|props| props.inner_ref::()) @@ -646,7 +646,7 @@ where let areas = props.layout.split(render.area); for (index, area) in areas.iter().enumerate() { - if let Some(section) = self.sections.get(index) { + if let Some(section) = self.sections.get_mut(index) { let focus = self .state .focus diff --git a/src/ui/widget/input.rs b/src/ui/widget/input.rs index 08a40244..4e978391 100644 --- a/src/ui/widget/input.rs +++ b/src/ui/widget/input.rs @@ -227,7 +227,7 @@ where self.state.text = Some(props.text.clone()); } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = TextFieldProps::default(); let props = props .and_then(|props| props.inner_ref::()) diff --git a/src/ui/widget/list.rs b/src/ui/widget/list.rs index bfb74822..9eb6b9d0 100644 --- a/src/ui/widget/list.rs +++ b/src/ui/widget/list.rs @@ -216,7 +216,7 @@ where } } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = TableProps::default(); let props = props .and_then(|props| props.inner_ref::>()) @@ -261,7 +261,7 @@ where .column_spacing(1) .highlight_style(style::highlight()); - frame.render_stateful_widget(rows, render.area, &mut self.state.clone()); + frame.render_stateful_widget(rows, render.area, &mut self.state); } else { let center = layout::centered_rect(render.area, 50, 10); let hint = Text::from(span::default("Nothing to show")) diff --git a/src/ui/widget/text.rs b/src/ui/widget/text.rs index cf9fcfe8..85e73e6f 100644 --- a/src/ui/widget/text.rs +++ b/src/ui/widget/text.rs @@ -168,7 +168,7 @@ where None } - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let default = TextAreaProps::default(); let props = props .and_then(|props| props.inner_ref::()) diff --git a/src/ui/widget/window.rs b/src/ui/widget/window.rs index 6c0651ff..4ebf0ee4 100644 --- a/src/ui/widget/window.rs +++ b/src/ui/widget/window.rs @@ -98,7 +98,7 @@ where } } - fn render(&self, props: Option<&ViewProps>, _render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, _render: RenderProps, frame: &mut Frame) { let default = WindowProps::default(); let props = props .and_then(|props| props.inner_ref::>()) @@ -109,7 +109,7 @@ where let page = props .current_page .as_ref() - .and_then(|id| self.pages.get(id)); + .and_then(|id| self.pages.get_mut(id)); if let Some(page) = page { page.render(RenderProps::from(area).focus(true), frame); @@ -190,11 +190,11 @@ where } } - fn render(&self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, _props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { let [content_area, shortcuts_area] = Layout::vertical([Constraint::Min(1), Constraint::Length(1)]).areas(render.area); - if let Some(content) = self.content.as_ref() { + if let Some(content) = self.content.as_mut() { content.render( RenderProps::from(content_area) .layout(Layout::horizontal([Constraint::Min(1)])) @@ -203,7 +203,7 @@ where ); } - if let Some(shortcuts) = self.shortcuts.as_ref() { + if let Some(shortcuts) = self.shortcuts.as_mut() { shortcuts.render(RenderProps::from(shortcuts_area), frame); } } @@ -256,7 +256,7 @@ impl View for Shortcuts { type Message = M; type State = S; - fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { + fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) { use ratatui::widgets::Table; let default = ShortcutsProps::default();