Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
tui: Add context bar to patch view page
Erik Kundt committed 3 years ago
commit f287b622596b0eaa5cb527b51dafbdf2c35af5af
parent 01ded9b0f2171e9d59087e9a10aad29ad9922dd6
5 files changed +160 -33
modified radicle-tui/src/app.rs
@@ -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
modified radicle-tui/src/ui.rs
@@ -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,
modified radicle-tui/src/ui/cob/patch.rs
@@ -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)
}

modified radicle-tui/src/ui/components/context.rs
@@ -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
+
    }
+
}
modified radicle-tui/src/ui/layout.rs
@@ -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);