tui: Add issue page with placeholder
This commit is contained in:
parent
56a61b61c9
commit
21533f37eb
|
|
@ -4,6 +4,7 @@ pub mod subscription;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use radicle::cob::issue::{IssueId, Issues};
|
||||||
use radicle::cob::patch::{PatchId, Patches};
|
use radicle::cob::patch::{PatchId, Patches};
|
||||||
use radicle::identity::{Id, Project};
|
use radicle::identity::{Id, Project};
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
|
|
@ -18,7 +19,7 @@ use radicle_tui::Tui;
|
||||||
|
|
||||||
use page::{HomeView, PatchView};
|
use page::{HomeView, PatchView};
|
||||||
|
|
||||||
use self::page::PageStack;
|
use self::page::{IssuePage, PageStack};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum HomeCid {
|
pub enum HomeCid {
|
||||||
|
|
@ -35,10 +36,16 @@ pub enum PatchCid {
|
||||||
Files,
|
Files,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
|
pub enum IssueCid {
|
||||||
|
List,
|
||||||
|
}
|
||||||
|
|
||||||
/// All component ids known to this application.
|
/// All component ids known to this application.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum Cid {
|
pub enum Cid {
|
||||||
Home(HomeCid),
|
Home(HomeCid),
|
||||||
|
Issue(IssueCid),
|
||||||
Patch(PatchCid),
|
Patch(PatchCid),
|
||||||
GlobalListener,
|
GlobalListener,
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +54,12 @@ pub enum Cid {
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum HomeMessage {}
|
pub enum HomeMessage {}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum IssueMessage {
|
||||||
|
Show(IssueId),
|
||||||
|
Leave,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum PatchMessage {
|
pub enum PatchMessage {
|
||||||
Show(PatchId),
|
Show(PatchId),
|
||||||
|
|
@ -56,6 +69,7 @@ pub enum PatchMessage {
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Home(HomeMessage),
|
Home(HomeMessage),
|
||||||
|
Issue(IssueMessage),
|
||||||
Patch(PatchMessage),
|
Patch(PatchMessage),
|
||||||
NavigationChanged(u16),
|
NavigationChanged(u16),
|
||||||
Tick,
|
Tick,
|
||||||
|
|
@ -128,6 +142,32 @@ impl App {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn view_issue(
|
||||||
|
&mut self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
id: IssueId,
|
||||||
|
theme: &Theme,
|
||||||
|
) -> Result<()> {
|
||||||
|
let repo = self
|
||||||
|
.context
|
||||||
|
.profile
|
||||||
|
.storage
|
||||||
|
.repository(self.context.id)
|
||||||
|
.unwrap();
|
||||||
|
let issues = Issues::open(&repo).unwrap();
|
||||||
|
|
||||||
|
if let Some(issue) = issues.get(&id)? {
|
||||||
|
let view = Box::new(IssuePage::new((id, issue)));
|
||||||
|
self.pages.push(view, app, &self.context, theme)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!(
|
||||||
|
"Could not mount 'page::IssueView'. Issue not found."
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tui<Cid, Message> for App {
|
impl Tui<Cid, Message> for App {
|
||||||
|
|
@ -153,6 +193,9 @@ impl Tui<Cid, Message> for App {
|
||||||
let theme = theme::default_dark();
|
let theme = theme::default_dark();
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
|
Message::Issue(IssueMessage::Show(id)) => {
|
||||||
|
self.view_issue(app, id, &theme)?;
|
||||||
|
}
|
||||||
Message::Patch(PatchMessage::Show(id)) => {
|
Message::Patch(PatchMessage::Show(id)) => {
|
||||||
self.view_patch(app, id, &theme)?;
|
self.view_patch(app, id, &theme)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use radicle_tui::ui::widget::patch;
|
||||||
|
|
||||||
use radicle_tui::ui::widget::Widget;
|
use radicle_tui::ui::widget::Widget;
|
||||||
|
|
||||||
use super::{Message, PatchMessage};
|
use super::{IssueMessage, Message, PatchMessage};
|
||||||
|
|
||||||
/// Since the framework does not know the type of messages that are being
|
/// Since the framework does not know the type of messages that are being
|
||||||
/// passed around in the app, the following handlers need to be implemented for
|
/// passed around in the app, the following handlers need to be implemented for
|
||||||
|
|
@ -83,7 +83,9 @@ impl tuirealm::Component<Message, NoUserEvent> for Widget<IssueBrowser> {
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => Some(Message::Tick),
|
}) => self
|
||||||
|
.selected_item()
|
||||||
|
.map(|item| Message::Issue(IssueMessage::Show(item.id()))),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use radicle::cob::patch::{Patch, PatchId};
|
use radicle::cob::{
|
||||||
|
issue::{Issue, IssueId},
|
||||||
|
patch::{Patch, PatchId},
|
||||||
|
};
|
||||||
use tuirealm::{Frame, NoUserEvent};
|
use tuirealm::{Frame, NoUserEvent};
|
||||||
|
|
||||||
use radicle_tui::ui::layout;
|
use radicle_tui::ui::layout;
|
||||||
use radicle_tui::ui::theme::Theme;
|
use radicle_tui::ui::theme::Theme;
|
||||||
use radicle_tui::ui::widget;
|
use radicle_tui::ui::widget;
|
||||||
|
|
||||||
use super::{subscription, Application, Cid, Context, HomeCid, Message, PatchCid};
|
use super::{subscription, Application, Cid, Context, HomeCid, IssueCid, Message, PatchCid};
|
||||||
|
|
||||||
/// `tuirealm`'s event and prop system is designed to work with flat component hierarchies.
|
/// `tuirealm`'s event and prop system is designed to work with flat component hierarchies.
|
||||||
/// Building deep nested component hierarchies would need a lot more additional effort to
|
/// Building deep nested component hierarchies would need a lot more additional effort to
|
||||||
|
|
@ -113,6 +116,63 @@ impl ViewPage for HomeView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Issue detail page
|
||||||
|
///
|
||||||
|
pub struct IssuePage {
|
||||||
|
active_component: Cid,
|
||||||
|
issue: (IssueId, Issue),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IssuePage {
|
||||||
|
pub fn new(issue: (IssueId, Issue)) -> Self {
|
||||||
|
IssuePage {
|
||||||
|
active_component: Cid::Issue(IssueCid::List),
|
||||||
|
issue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewPage for IssuePage {
|
||||||
|
fn mount(
|
||||||
|
&self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (id, issue) = &self.issue;
|
||||||
|
let list = widget::issue::list(theme, (*id, issue), &context.profile).to_boxed();
|
||||||
|
|
||||||
|
app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
|
||||||
|
app.active(&self.active_component)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmount(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||||
|
app.umount(&Cid::Issue(IssueCid::List))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&mut self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
_message: Message,
|
||||||
|
) -> Result<()> {
|
||||||
|
app.active(&self.active_component)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
||||||
|
let area = frame.size();
|
||||||
|
let layout = layout::default_page(area);
|
||||||
|
|
||||||
|
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
|
||||||
|
app.view(&self.active_component, frame, layout[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Patch detail page
|
/// Patch detail page
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,12 @@ pub struct IssueItem {
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IssueItem {
|
||||||
|
pub fn id(&self) -> IssueId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TryFrom<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
|
impl TryFrom<(&Profile, &Repository, IssueId, Issue)> for IssueItem {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
|
pub mod issue;
|
||||||
pub mod patch;
|
pub mod patch;
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
use radicle_cli::terminal::format;
|
||||||
|
|
||||||
|
use radicle::cob::issue::Issue;
|
||||||
|
use radicle::cob::issue::IssueId;
|
||||||
|
use radicle::Profile;
|
||||||
|
use tuirealm::props::Color;
|
||||||
|
|
||||||
|
use super::common;
|
||||||
|
use super::Widget;
|
||||||
|
|
||||||
|
use crate::ui::cob;
|
||||||
|
use crate::ui::theme::Theme;
|
||||||
|
use crate::ui::widget::common::context::ContextBar;
|
||||||
|
use crate::ui::widget::patch::Activity;
|
||||||
|
|
||||||
|
pub fn list(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<Activity> {
|
||||||
|
let (id, issue) = issue;
|
||||||
|
let shortcuts = common::shortcuts(
|
||||||
|
theme,
|
||||||
|
vec![
|
||||||
|
common::shortcut(theme, "esc", "back"),
|
||||||
|
common::shortcut(theme, "q", "quit"),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let context = context(theme, (id, issue), profile);
|
||||||
|
|
||||||
|
let not_implemented = common::label("not implemented").foreground(theme.colors.default_fg);
|
||||||
|
let activity = Activity::new(not_implemented, context, shortcuts);
|
||||||
|
|
||||||
|
Widget::new(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(theme: &Theme, issue: (IssueId, &Issue), profile: &Profile) -> Widget<ContextBar> {
|
||||||
|
let (id, issue) = issue;
|
||||||
|
let is_you = *issue.author().id() == profile.did();
|
||||||
|
|
||||||
|
let id = format::cob(&id);
|
||||||
|
let title = issue.title();
|
||||||
|
let author = cob::format_author(issue.author().id(), is_you);
|
||||||
|
let comments = issue.comments().count();
|
||||||
|
|
||||||
|
let context = common::label(" issue ").background(theme.colors.context_badge_bg);
|
||||||
|
let id = common::label(&format!(" {id} "))
|
||||||
|
.foreground(theme.colors.context_id_fg)
|
||||||
|
.background(theme.colors.context_id_bg);
|
||||||
|
let title = common::label(&format!(" {title} "))
|
||||||
|
.foreground(theme.colors.default_fg)
|
||||||
|
.background(theme.colors.context_bg);
|
||||||
|
let author = common::label(&format!(" {author} "))
|
||||||
|
.foreground(theme.colors.context_id_author_fg)
|
||||||
|
.background(theme.colors.context_bg);
|
||||||
|
let comments = common::label(&format!(" {comments} "))
|
||||||
|
.foreground(Color::Rgb(70, 70, 70))
|
||||||
|
.background(theme.colors.context_light_bg);
|
||||||
|
|
||||||
|
let context_bar = ContextBar::new(context, id, author, title, comments);
|
||||||
|
|
||||||
|
Widget::new(context_bar).height(1)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue