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::subs;
|
||||||
use radicle_tui::ui;
|
use radicle_tui::ui;
|
||||||
use radicle_tui::ui::components::container::{GlobalListener, LabeledContainer, Tabs};
|
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::list::PropertyList;
|
||||||
use radicle_tui::ui::components::workspace::{Browser, PatchActivity, PatchFiles};
|
use radicle_tui::ui::components::workspace::{Browser, PatchActivity, PatchFiles};
|
||||||
use radicle_tui::ui::layout;
|
use radicle_tui::ui::layout;
|
||||||
|
|
@ -40,6 +40,7 @@ pub enum PatchCid {
|
||||||
Navigation,
|
Navigation,
|
||||||
Activity,
|
Activity,
|
||||||
Files,
|
Files,
|
||||||
|
Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All component ids known to this application.
|
/// 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::Dashboard), dashboard, vec![])?;
|
||||||
app.remount(
|
app.remount(Cid::Home(HomeCid::IssueBrowser), issue_browser, vec![])?;
|
||||||
Cid::Home(HomeCid::IssueBrowser),
|
app.remount(Cid::Home(HomeCid::PatchBrowser), patch_browser, vec![])?;
|
||||||
issue_browser,
|
|
||||||
vec![],
|
|
||||||
)?;
|
|
||||||
app.remount(
|
|
||||||
Cid::Home(HomeCid::PatchBrowser),
|
|
||||||
patch_browser,
|
|
||||||
vec![],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
|
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -285,11 +278,7 @@ impl ViewPage for Home {
|
||||||
.unwrap_size();
|
.unwrap_size();
|
||||||
let layout = layout::default_page(area, navigation_h, shortcuts_h);
|
let layout = layout::default_page(area, navigation_h, shortcuts_h);
|
||||||
|
|
||||||
app.view(
|
app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]);
|
||||||
&Cid::Home(HomeCid::Navigation),
|
|
||||||
frame,
|
|
||||||
layout[0],
|
|
||||||
);
|
|
||||||
app.view(&self.active_component, frame, layout[1]);
|
app.view(&self.active_component, frame, layout[1]);
|
||||||
app.view(&Cid::Shortcuts, frame, layout[2]);
|
app.view(&Cid::Shortcuts, frame, layout[2]);
|
||||||
}
|
}
|
||||||
|
|
@ -322,10 +311,11 @@ impl ViewPage for PatchView {
|
||||||
context: &Context,
|
context: &Context,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) -> Result<()> {
|
) -> 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 navigation = ui::patch_navigation(theme).to_boxed();
|
||||||
let activity = ui::patch_activity(theme).to_boxed();
|
let activity = ui::patch_activity(theme).to_boxed();
|
||||||
let files = ui::patch_files(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(
|
let shortcuts = ui::shortcuts(
|
||||||
theme,
|
theme,
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -343,6 +333,7 @@ impl ViewPage for PatchView {
|
||||||
)?;
|
)?;
|
||||||
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
|
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
|
||||||
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
|
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
|
||||||
|
app.remount(Cid::Patch(PatchCid::Context), context, vec![])?;
|
||||||
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
|
app.remount(Cid::Shortcuts, shortcuts, vec![])?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -361,21 +352,19 @@ impl ViewPage for PatchView {
|
||||||
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
||||||
let area = frame.size();
|
let area = frame.size();
|
||||||
let navigation_h = 2u16;
|
let navigation_h = 2u16;
|
||||||
|
let context_h = 1u16;
|
||||||
let shortcuts_h = app
|
let shortcuts_h = app
|
||||||
.query(&Cid::Shortcuts, Attribute::Height)
|
.query(&Cid::Shortcuts, Attribute::Height)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.unwrap_or(AttrValue::Size(0))
|
.unwrap_or(AttrValue::Size(0))
|
||||||
.unwrap_size();
|
.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(
|
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
|
||||||
&Cid::Patch(PatchCid::Navigation),
|
|
||||||
frame,
|
|
||||||
layout[0],
|
|
||||||
);
|
|
||||||
app.view(&self.active_component, frame, layout[1]);
|
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<()> {
|
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> {
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<Shortcuts> {
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ pub mod widget;
|
||||||
|
|
||||||
use radicle::prelude::{Id, Project};
|
use radicle::prelude::{Id, Project};
|
||||||
use radicle::Profile;
|
use radicle::Profile;
|
||||||
use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, TextSpan};
|
use tuirealm::props::{AttrValue, Attribute, Color, PropPayload, PropValue, TextSpan};
|
||||||
use tuirealm::MockComponent;
|
use tuirealm::MockComponent;
|
||||||
|
|
||||||
use radicle::cob::patch::{Patch, PatchId};
|
use radicle::cob::patch::{Patch, PatchId};
|
||||||
|
|
@ -19,8 +19,11 @@ use components::list::{Property, PropertyList};
|
||||||
|
|
||||||
use widget::Widget;
|
use widget::Widget;
|
||||||
|
|
||||||
|
use self::cob::patch;
|
||||||
|
use self::components::context::ContextBar;
|
||||||
use self::components::list::{List, Table};
|
use self::components::list::{List, Table};
|
||||||
use self::components::workspace::{Browser, PatchActivity, PatchFiles};
|
use self::components::workspace::{Browser, PatchActivity, PatchFiles};
|
||||||
|
use self::theme::Theme;
|
||||||
|
|
||||||
pub fn global_listener() -> Widget<GlobalListener> {
|
pub fn global_listener() -> Widget<GlobalListener> {
|
||||||
Widget::new(GlobalListener::default())
|
Widget::new(GlobalListener::default())
|
||||||
|
|
@ -162,6 +165,35 @@ pub fn patch_files(theme: &theme::Theme) -> Widget<PatchFiles> {
|
||||||
Widget::new(files)
|
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> {
|
pub fn home_navigation(theme: &theme::Theme) -> Widget<Tabs> {
|
||||||
tabs(
|
tabs(
|
||||||
theme,
|
theme,
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,19 @@ use tuirealm::props::{Color, TextSpan};
|
||||||
use crate::ui::components::list::List;
|
use crate::ui::components::list::List;
|
||||||
use crate::ui::theme::Theme;
|
use crate::ui::theme::Theme;
|
||||||
|
|
||||||
fn format_status(_patch: &Patch) -> String {
|
pub fn format_status(_patch: &Patch) -> String {
|
||||||
String::from(" ⏺ ")
|
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()
|
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 author_did = patch.author().id();
|
||||||
let start = &author_did.to_human()[0..4];
|
let start = &author_did.to_human()[0..4];
|
||||||
let end = &author_did.to_human()[43..47];
|
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<_>>())
|
format!("{:?}", patch.tags().collect::<Vec<_>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_timestamp(patch: &Patch) -> String {
|
pub fn format_timestamp(patch: &Patch) -> String {
|
||||||
let fmt = timeago::Formatter::new();
|
let fmt = timeago::Formatter::new();
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
|
@ -44,9 +48,11 @@ fn format_timestamp(patch: &Patch) -> String {
|
||||||
fmt.convert(Duration::from_secs(now - patch.timestamp().as_secs()))
|
fmt.convert(Duration::from_secs(now - patch.timestamp().as_secs()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_comments(patch: &Patch) -> String {
|
pub fn format_comments(patch: &Patch) -> String {
|
||||||
let (_, revision) = patch.latest().unwrap();
|
let count = match patch.latest() {
|
||||||
let count = revision.discussion().len();
|
Some((_, rev)) => rev.discussion().len(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
format!("{}", count)
|
format!("{}", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,3 +102,73 @@ impl WidgetComponent for Shortcuts {
|
||||||
CmdResult::None
|
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)
|
.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 {
|
pub fn centered_label(label_w: u16, area: Rect) -> Rect {
|
||||||
let label_h = 1u16;
|
let label_h = 1u16;
|
||||||
let spacer_w = area.width.saturating_sub(label_w).saturating_div(2);
|
let spacer_w = area.width.saturating_sub(label_w).saturating_div(2);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue