tui: Add context bar to patch view page
This commit is contained in:
parent
0f52af8b2e
commit
0381aa3281
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue