tui: Add context bar to patch view page

This commit is contained in:
Erik Kundt 2023-04-06 21:01:28 +02:00
parent 0f52af8b2e
commit 0381aa3281
5 changed files with 160 additions and 33 deletions

View File

@ -14,7 +14,7 @@ use radicle_tui::cob::patch::{self};
use radicle_tui::subs;
use radicle_tui::ui;
use radicle_tui::ui::components::container::{GlobalListener, LabeledContainer, Tabs};
use radicle_tui::ui::components::context::Shortcuts;
use radicle_tui::ui::components::context::{ContextBar, Shortcuts};
use radicle_tui::ui::components::list::PropertyList;
use radicle_tui::ui::components::workspace::{Browser, PatchActivity, PatchFiles};
use radicle_tui::ui::layout;
@ -40,6 +40,7 @@ pub enum PatchCid {
Navigation,
Activity,
Files,
Context,
}
/// All component ids known to this application.
@ -248,16 +249,8 @@ impl ViewPage for Home {
)?;
app.remount(Cid::Home(HomeCid::Dashboard), dashboard, vec![])?;
app.remount(
Cid::Home(HomeCid::IssueBrowser),
issue_browser,
vec![],
)?;
app.remount(
Cid::Home(HomeCid::PatchBrowser),
patch_browser,
vec![],
)?;
app.remount(Cid::Home(HomeCid::IssueBrowser), issue_browser, vec![])?;
app.remount(Cid::Home(HomeCid::PatchBrowser), patch_browser, vec![])?;
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
Ok(())
@ -285,11 +278,7 @@ impl ViewPage for Home {
.unwrap_size();
let layout = layout::default_page(area, navigation_h, shortcuts_h);
app.view(
&Cid::Home(HomeCid::Navigation),
frame,
layout[0],
);
app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]);
app.view(&self.active_component, frame, layout[1]);
app.view(&Cid::Shortcuts, frame, layout[2]);
}
@ -322,10 +311,11 @@ impl ViewPage for PatchView {
context: &Context,
theme: &Theme,
) -> Result<()> {
if let Some((_, _)) = context.patches.get(context.selected_patch) {
if let Some((id, patch)) = context.patches.get(context.selected_patch) {
let navigation = ui::patch_navigation(theme).to_boxed();
let activity = ui::patch_activity(theme).to_boxed();
let files = ui::patch_files(theme).to_boxed();
let context = ui::patch_context(theme, (*id, patch), &context.profile).to_boxed();
let shortcuts = ui::shortcuts(
theme,
vec![
@ -343,6 +333,7 @@ impl ViewPage for PatchView {
)?;
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
app.remount(Cid::Patch(PatchCid::Context), context, vec![])?;
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
}
Ok(())
@ -361,21 +352,19 @@ impl ViewPage for PatchView {
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
let area = frame.size();
let navigation_h = 2u16;
let context_h = 1u16;
let shortcuts_h = app
.query(&Cid::Shortcuts, Attribute::Height)
.ok()
.flatten()
.unwrap_or(AttrValue::Size(0))
.unwrap_size();
let layout = layout::default_page(area, navigation_h, shortcuts_h);
let layout = layout::page_with_context(area, navigation_h, context_h, shortcuts_h);
app.view(
&Cid::Patch(PatchCid::Navigation),
frame,
layout[0],
);
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
app.view(&self.active_component, frame, layout[1]);
app.view(&Cid::Shortcuts, frame, layout[2]);
app.view(&Cid::Patch(PatchCid::Context), frame, layout[2]);
app.view(&Cid::Shortcuts, frame, layout[3]);
}
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
@ -475,6 +464,12 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<PropertyList> {
}
}
impl tuirealm::Component<Message, NoUserEvent> for Widget<ContextBar> {
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
None
}
}
impl tuirealm::Component<Message, NoUserEvent> for Widget<Shortcuts> {
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
None

View File

@ -7,7 +7,7 @@ pub mod widget;
use radicle::prelude::{Id, Project};
use radicle::Profile;
use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, TextSpan};
use tuirealm::props::{AttrValue, Attribute, Color, PropPayload, PropValue, TextSpan};
use tuirealm::MockComponent;
use radicle::cob::patch::{Patch, PatchId};
@ -19,8 +19,11 @@ use components::list::{Property, PropertyList};
use widget::Widget;
use self::cob::patch;
use self::components::context::ContextBar;
use self::components::list::{List, Table};
use self::components::workspace::{Browser, PatchActivity, PatchFiles};
use self::theme::Theme;
pub fn global_listener() -> Widget<GlobalListener> {
Widget::new(GlobalListener::default())
@ -162,6 +165,35 @@ pub fn patch_files(theme: &theme::Theme) -> Widget<PatchFiles> {
Widget::new(files)
}
pub fn patch_context(
_theme: &Theme,
patch: (PatchId, &Patch),
profile: &Profile,
) -> Widget<ContextBar> {
let (id, patch) = patch;
let id = patch::format_id(id);
let title = patch.title();
let author = patch::format_author(patch, profile);
let comments = patch::format_comments(patch);
let context = label(" patch ").background(Color::Rgb(238, 111, 248));
let id = label(&format!(" {} ", id))
.foreground(Color::Rgb(117, 113, 249))
.background(Color::Rgb(40, 40, 40));
let title = label(&format!(" {} ", title))
.foreground(Color::Rgb(70, 70, 70))
.background(Color::Rgb(40, 40, 40));
let author = label(&format!(" {} ", author))
.foreground(Color::Rgb(117, 113, 249))
.background(Color::Rgb(40, 40, 40));
let comments = label(&format!(" {} ", comments))
.foreground(Color::Rgb(70, 70, 70))
.background(Color::Rgb(50, 50, 50));
let context_bar = ContextBar::new(context, id, author, title, comments);
Widget::new(context_bar)
}
pub fn home_navigation(theme: &theme::Theme) -> Widget<Tabs> {
tabs(
theme,

View File

@ -10,15 +10,19 @@ use tuirealm::props::{Color, TextSpan};
use crate::ui::components::list::List;
use crate::ui::theme::Theme;
fn format_status(_patch: &Patch) -> String {
pub fn format_status(_patch: &Patch) -> String {
String::from("")
}
fn format_title(patch: &Patch) -> String {
pub fn format_id(id: PatchId) -> String {
id.to_string()[0..10].to_string()
}
pub fn format_title(patch: &Patch) -> String {
patch.title().to_string()
}
fn format_author(patch: &Patch, profile: &Profile) -> String {
pub fn format_author(patch: &Patch, profile: &Profile) -> String {
let author_did = patch.author().id();
let start = &author_did.to_human()[0..4];
let end = &author_did.to_human()[43..47];
@ -30,11 +34,11 @@ fn format_author(patch: &Patch, profile: &Profile) -> String {
}
}
fn format_tags(patch: &Patch) -> String {
pub fn format_tags(patch: &Patch) -> String {
format!("{:?}", patch.tags().collect::<Vec<_>>())
}
fn format_timestamp(patch: &Patch) -> String {
pub fn format_timestamp(patch: &Patch) -> String {
let fmt = timeago::Formatter::new();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -44,9 +48,11 @@ fn format_timestamp(patch: &Patch) -> String {
fmt.convert(Duration::from_secs(now - patch.timestamp().as_secs()))
}
fn format_comments(patch: &Patch) -> String {
let (_, revision) = patch.latest().unwrap();
let count = revision.discussion().len();
pub fn format_comments(patch: &Patch) -> String {
let count = match patch.latest() {
Some((_, rev)) => rev.discussion().len(),
None => 0,
};
format!("{}", count)
}

View File

@ -102,3 +102,73 @@ impl WidgetComponent for Shortcuts {
CmdResult::None
}
}
pub struct ContextBar {
context: Widget<Label>,
id: Widget<Label>,
author: Widget<Label>,
title: Widget<Label>,
comments: Widget<Label>,
}
impl ContextBar {
pub fn new(
context: Widget<Label>,
id: Widget<Label>,
author: Widget<Label>,
title: Widget<Label>,
comments: Widget<Label>,
) -> Self {
Self {
context,
id,
author,
title,
comments,
}
}
}
impl WidgetComponent for ContextBar {
fn view(&mut self, properties: &Props, frame: &mut Frame, area: Rect) {
let display = properties
.get_or(Attribute::Display, AttrValue::Flag(true))
.unwrap_flag();
let context_w = self.context.query(Attribute::Width).unwrap().unwrap_size();
let id_w = self.id.query(Attribute::Width).unwrap().unwrap_size();
let author_w = self.author.query(Attribute::Width).unwrap().unwrap_size();
let count_w = self.comments.query(Attribute::Width).unwrap().unwrap_size();
if display {
let layout = layout::h_stack(
vec![
self.context.clone().to_boxed(),
self.id.clone().to_boxed(),
self.title
.clone()
.width(
area.width
.saturating_sub(context_w + id_w + author_w + count_w),
)
.to_boxed(),
self.author.clone().to_boxed(),
self.comments.clone().to_boxed(),
],
area,
);
for (mut component, area) in layout {
component.view(frame, area);
}
}
}
fn state(&self) -> State {
State::None
}
fn perform(&mut self, _properties: &Props, _cmd: Cmd) -> CmdResult {
CmdResult::None
}
}

View File

@ -66,6 +66,30 @@ pub fn default_page(area: Rect, nav_h: u16, shortcuts_h: u16) -> Vec<Rect> {
.split(area)
}
pub fn page_with_context(area: Rect, nav_h: u16, context_h: u16, shortcuts_h: u16) -> Vec<Rect> {
let margin_h = 1u16;
let content_h = area.height.saturating_sub(
shortcuts_h
.saturating_add(nav_h)
.saturating_add(context_h)
.saturating_add(margin_h),
);
Layout::default()
.direction(Direction::Vertical)
.horizontal_margin(margin_h)
.constraints(
[
Constraint::Length(nav_h),
Constraint::Length(content_h),
Constraint::Length(context_h),
Constraint::Length(shortcuts_h),
]
.as_ref(),
)
.split(area)
}
pub fn centered_label(label_w: u16, area: Rect) -> Rect {
let label_h = 1u16;
let spacer_w = area.width.saturating_sub(label_w).saturating_div(2);