tui: Split application into separate modules
This commit is contained in:
parent
969219c51d
commit
a2da673c31
|
|
@ -1,31 +1,22 @@
|
||||||
|
pub mod event;
|
||||||
|
pub mod page;
|
||||||
|
pub mod subscription;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use tui_realm_stdlib::Phantom;
|
|
||||||
use tuirealm::application::PollStrategy;
|
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction as MoveDirection};
|
|
||||||
use tuirealm::event::{Event, Key, KeyEvent};
|
|
||||||
use tuirealm::{Application, Frame, MockComponent, NoUserEvent, State, StateValue};
|
|
||||||
|
|
||||||
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::{ContextBar, Shortcuts};
|
|
||||||
use radicle_tui::ui::components::list::PropertyList;
|
|
||||||
use radicle_tui::ui::components::workspace::{
|
|
||||||
Browser, Dashboard, IssueBrowser, PatchActivity, PatchFiles,
|
|
||||||
};
|
|
||||||
use radicle_tui::ui::layout;
|
|
||||||
use radicle_tui::ui::theme::{self, Theme};
|
|
||||||
use radicle_tui::ui::widget::{self, Widget};
|
|
||||||
|
|
||||||
use radicle_tui::Tui;
|
|
||||||
|
|
||||||
use radicle::cob::patch::{Patch, PatchId};
|
use radicle::cob::patch::{Patch, PatchId};
|
||||||
use radicle::identity::{Id, Project};
|
use radicle::identity::{Id, Project};
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
|
|
||||||
|
use tuirealm::application::PollStrategy;
|
||||||
|
use tuirealm::{Application, Frame, NoUserEvent};
|
||||||
|
|
||||||
|
use radicle_tui::cob::patch::{self};
|
||||||
|
|
||||||
|
use radicle_tui::ui;
|
||||||
|
use radicle_tui::ui::theme::{self, Theme};
|
||||||
|
use radicle_tui::Tui;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum HomeCid {
|
pub enum HomeCid {
|
||||||
Navigation,
|
Navigation,
|
||||||
|
|
@ -81,7 +72,7 @@ pub struct Context {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
context: Context,
|
context: Context,
|
||||||
active_page: Box<dyn ViewPage>,
|
active_page: Box<dyn page::ViewPage>,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
quit: bool,
|
quit: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -100,7 +91,7 @@ impl App {
|
||||||
patches,
|
patches,
|
||||||
},
|
},
|
||||||
theme: theme::default_dark(),
|
theme: theme::default_dark(),
|
||||||
active_page: Box::<Home>::default(),
|
active_page: Box::<page::Home>::default(),
|
||||||
quit: false,
|
quit: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +101,7 @@ impl App {
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.active_page = Box::<Home>::default();
|
self.active_page = Box::<page::Home>::default();
|
||||||
self.active_page.mount(app, &self.context, theme)?;
|
self.active_page.mount(app, &self.context, theme)?;
|
||||||
self.active_page.activate(app)?;
|
self.active_page.activate(app)?;
|
||||||
|
|
||||||
|
|
@ -122,7 +113,7 @@ impl App {
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.active_page = Box::<PatchView>::default();
|
self.active_page = Box::<page::PatchView>::default();
|
||||||
self.active_page.mount(app, &self.context, theme)?;
|
self.active_page.mount(app, &self.context, theme)?;
|
||||||
self.active_page.activate(app)?;
|
self.active_page.activate(app)?;
|
||||||
|
|
||||||
|
|
@ -136,7 +127,7 @@ impl Tui<Cid, Message> for App {
|
||||||
|
|
||||||
// Add global key listener and subscribe to key events
|
// Add global key listener and subscribe to key events
|
||||||
let global = ui::widget::common::global_listener().to_boxed();
|
let global = ui::widget::common::global_listener().to_boxed();
|
||||||
app.mount(Cid::GlobalListener, global, subs::global())?;
|
app.mount(Cid::GlobalListener, global, subscription::global())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -178,280 +169,3 @@ impl Tui<Cid, Message> for App {
|
||||||
self.quit
|
self.quit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `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
|
|
||||||
/// properly pass events and props down these hierarchies. This makes it hard to implement
|
|
||||||
/// full app views (home, patch details etc) as components.
|
|
||||||
///
|
|
||||||
/// View pages take into account these flat component hierarchies, and provide
|
|
||||||
/// switchable sets of components.
|
|
||||||
pub trait ViewPage {
|
|
||||||
fn mount(
|
|
||||||
&self,
|
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
|
||||||
context: &Context,
|
|
||||||
theme: &Theme,
|
|
||||||
) -> Result<()>;
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message);
|
|
||||||
|
|
||||||
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame);
|
|
||||||
|
|
||||||
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Home
|
|
||||||
///
|
|
||||||
pub struct Home {
|
|
||||||
active_component: Cid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Home {
|
|
||||||
fn default() -> Self {
|
|
||||||
Home {
|
|
||||||
active_component: Cid::Home(HomeCid::Dashboard),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewPage for Home {
|
|
||||||
fn mount(
|
|
||||||
&self,
|
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
|
||||||
context: &Context,
|
|
||||||
theme: &Theme,
|
|
||||||
) -> Result<()> {
|
|
||||||
let navigation = widget::home::navigation(theme).to_boxed();
|
|
||||||
|
|
||||||
let dashboard = widget::home::dashboard(theme, &context.id, &context.project).to_boxed();
|
|
||||||
let issue_browser = widget::home::issues(theme).to_boxed();
|
|
||||||
let patch_browser =
|
|
||||||
widget::home::patches(theme, &context.patches, &context.profile).to_boxed();
|
|
||||||
|
|
||||||
app.remount(
|
|
||||||
Cid::Home(HomeCid::Navigation),
|
|
||||||
navigation,
|
|
||||||
subs::navigation(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
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![])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
|
||||||
if let Message::NavigationChanged(index) = message {
|
|
||||||
self.active_component = match index {
|
|
||||||
0 => Cid::Home(HomeCid::Dashboard),
|
|
||||||
1 => Cid::Home(HomeCid::IssueBrowser),
|
|
||||||
2 => Cid::Home(HomeCid::PatchBrowser),
|
|
||||||
_ => Cid::Home(HomeCid::Dashboard),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
|
||||||
let area = frame.size();
|
|
||||||
let navigation_h = 2u16;
|
|
||||||
let layout = layout::default_page(area, navigation_h);
|
|
||||||
|
|
||||||
app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]);
|
|
||||||
app.view(&self.active_component, frame, layout[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
|
||||||
app.active(&self.active_component)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Patch detail page
|
|
||||||
///
|
|
||||||
pub struct PatchView {
|
|
||||||
active_component: Cid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PatchView {
|
|
||||||
fn default() -> Self {
|
|
||||||
PatchView {
|
|
||||||
active_component: Cid::Patch(PatchCid::Activity),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewPage for PatchView {
|
|
||||||
fn mount(
|
|
||||||
&self,
|
|
||||||
app: &mut Application<Cid, Message, NoUserEvent>,
|
|
||||||
context: &Context,
|
|
||||||
theme: &Theme,
|
|
||||||
) -> Result<()> {
|
|
||||||
if let Some((id, patch)) = context.patches.get(context.selected_patch) {
|
|
||||||
let navigation = widget::patch::navigation(theme).to_boxed();
|
|
||||||
let activity =
|
|
||||||
widget::patch::activity(theme, (*id, patch), &context.profile).to_boxed();
|
|
||||||
let files = widget::patch::files(theme, (*id, patch), &context.profile).to_boxed();
|
|
||||||
|
|
||||||
app.remount(
|
|
||||||
Cid::Patch(PatchCid::Navigation),
|
|
||||||
navigation,
|
|
||||||
subs::navigation(),
|
|
||||||
)?;
|
|
||||||
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
|
|
||||||
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, message: Message) {
|
|
||||||
if let Message::NavigationChanged(index) = message {
|
|
||||||
self.active_component = match index {
|
|
||||||
0 => Cid::Patch(PatchCid::Activity),
|
|
||||||
1 => Cid::Patch(PatchCid::Files),
|
|
||||||
_ => Cid::Patch(PatchCid::Activity),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
|
||||||
let area = frame.size();
|
|
||||||
let navigation_h = 2u16;
|
|
||||||
|
|
||||||
let layout = layout::default_page(area, navigation_h);
|
|
||||||
|
|
||||||
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
|
|
||||||
app.view(&self.active_component, frame, layout[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
|
||||||
app.active(&self.active_component)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// each component used.
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<GlobalListener> {
|
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
Event::WindowResize(_, _) => Some(Message::Tick),
|
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::Char('q'),
|
|
||||||
..
|
|
||||||
}) => Some(Message::Quit),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<Tabs> {
|
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
|
||||||
match self.perform(Cmd::Move(MoveDirection::Right)) {
|
|
||||||
CmdResult::Changed(State::One(StateValue::U16(index))) => {
|
|
||||||
Some(Message::NavigationChanged(index))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<Browser<(PatchId, Patch)>> {
|
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
|
||||||
self.perform(Cmd::Move(MoveDirection::Up));
|
|
||||||
Some(Message::Tick)
|
|
||||||
}
|
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::Down, ..
|
|
||||||
}) => {
|
|
||||||
self.perform(Cmd::Move(MoveDirection::Down));
|
|
||||||
Some(Message::Tick)
|
|
||||||
}
|
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::Enter, ..
|
|
||||||
}) => match self.perform(Cmd::Submit) {
|
|
||||||
CmdResult::Submit(State::One(StateValue::Usize(index))) => {
|
|
||||||
Some(Message::Patch(PatchMessage::Show(index)))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<Dashboard> {
|
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<IssueBrowser> {
|
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchActivity> {
|
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
|
||||||
Some(Message::Patch(PatchMessage::Leave))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchFiles> {
|
|
||||||
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
|
||||||
Some(Message::Patch(PatchMessage::Leave))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<LabeledContainer> {
|
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Widget<PropertyList> {
|
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tuirealm::Component<Message, NoUserEvent> for Phantom {
|
|
||||||
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
use radicle::cob::patch::{Patch, PatchId};
|
||||||
|
|
||||||
|
use tui_realm_stdlib::Phantom;
|
||||||
|
use tuirealm::command::{Cmd, CmdResult, Direction as MoveDirection};
|
||||||
|
use tuirealm::event::{Event, Key, KeyEvent};
|
||||||
|
use tuirealm::{MockComponent, NoUserEvent, State, StateValue};
|
||||||
|
|
||||||
|
use radicle_tui::ui::components::container::{GlobalListener, LabeledContainer, Tabs};
|
||||||
|
use radicle_tui::ui::components::context::{ContextBar, Shortcuts};
|
||||||
|
use radicle_tui::ui::components::list::PropertyList;
|
||||||
|
use radicle_tui::ui::components::workspace::{
|
||||||
|
Browser, Dashboard, IssueBrowser, PatchActivity, PatchFiles,
|
||||||
|
};
|
||||||
|
|
||||||
|
use radicle_tui::ui::widget::Widget;
|
||||||
|
|
||||||
|
use super::{Message, PatchMessage};
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// each component used.
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<GlobalListener> {
|
||||||
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
match event {
|
||||||
|
Event::WindowResize(_, _) => Some(Message::Tick),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('q'),
|
||||||
|
..
|
||||||
|
}) => Some(Message::Quit),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<Tabs> {
|
||||||
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
match event {
|
||||||
|
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
||||||
|
match self.perform(Cmd::Move(MoveDirection::Right)) {
|
||||||
|
CmdResult::Changed(State::One(StateValue::U16(index))) => {
|
||||||
|
Some(Message::NavigationChanged(index))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<Browser<(PatchId, Patch)>> {
|
||||||
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
match event {
|
||||||
|
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||||
|
self.perform(Cmd::Move(MoveDirection::Up));
|
||||||
|
Some(Message::Tick)
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Down, ..
|
||||||
|
}) => {
|
||||||
|
self.perform(Cmd::Move(MoveDirection::Down));
|
||||||
|
Some(Message::Tick)
|
||||||
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Enter, ..
|
||||||
|
}) => match self.perform(Cmd::Submit) {
|
||||||
|
CmdResult::Submit(State::One(StateValue::Usize(index))) => {
|
||||||
|
Some(Message::Patch(PatchMessage::Show(index)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<Dashboard> {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<IssueBrowser> {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchActivity> {
|
||||||
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
match event {
|
||||||
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
|
Some(Message::Patch(PatchMessage::Leave))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<PatchFiles> {
|
||||||
|
fn on(&mut self, event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
match event {
|
||||||
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
|
Some(Message::Patch(PatchMessage::Leave))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<LabeledContainer> {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Widget<PropertyList> {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tuirealm::Component<Message, NoUserEvent> for Phantom {
|
||||||
|
fn on(&mut self, _event: Event<NoUserEvent>) -> Option<Message> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use radicle_tui::ui::{layout, theme::Theme, widget};
|
||||||
|
use tuirealm::{Frame, NoUserEvent};
|
||||||
|
|
||||||
|
use super::{subscription, Application, Cid, Context, HomeCid, Message, PatchCid};
|
||||||
|
|
||||||
|
/// `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
|
||||||
|
/// properly pass events and props down these hierarchies. This makes it hard to implement
|
||||||
|
/// full app views (home, patch details etc) as components.
|
||||||
|
///
|
||||||
|
/// View pages take into account these flat component hierarchies, and provide
|
||||||
|
/// switchable sets of components.
|
||||||
|
pub trait ViewPage {
|
||||||
|
fn mount(
|
||||||
|
&self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message);
|
||||||
|
|
||||||
|
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame);
|
||||||
|
|
||||||
|
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Home
|
||||||
|
///
|
||||||
|
pub struct Home {
|
||||||
|
active_component: Cid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Home {
|
||||||
|
fn default() -> Self {
|
||||||
|
Home {
|
||||||
|
active_component: Cid::Home(HomeCid::Dashboard),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewPage for Home {
|
||||||
|
fn mount(
|
||||||
|
&self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
|
) -> Result<()> {
|
||||||
|
let navigation = widget::home::navigation(theme).to_boxed();
|
||||||
|
|
||||||
|
let dashboard = widget::home::dashboard(theme, &context.id, &context.project).to_boxed();
|
||||||
|
let issue_browser = widget::home::issues(theme).to_boxed();
|
||||||
|
let patch_browser =
|
||||||
|
widget::home::patches(theme, &context.patches, &context.profile).to_boxed();
|
||||||
|
|
||||||
|
app.remount(
|
||||||
|
Cid::Home(HomeCid::Navigation),
|
||||||
|
navigation,
|
||||||
|
subscription::navigation(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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![])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
if let Message::NavigationChanged(index) = message {
|
||||||
|
self.active_component = match index {
|
||||||
|
0 => Cid::Home(HomeCid::Dashboard),
|
||||||
|
1 => Cid::Home(HomeCid::IssueBrowser),
|
||||||
|
2 => Cid::Home(HomeCid::PatchBrowser),
|
||||||
|
_ => Cid::Home(HomeCid::Dashboard),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
||||||
|
let area = frame.size();
|
||||||
|
let navigation_h = 2u16;
|
||||||
|
let layout = layout::default_page(area, navigation_h);
|
||||||
|
|
||||||
|
app.view(&Cid::Home(HomeCid::Navigation), frame, layout[0]);
|
||||||
|
app.view(&self.active_component, frame, layout[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||||
|
app.active(&self.active_component)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Patch detail page
|
||||||
|
///
|
||||||
|
pub struct PatchView {
|
||||||
|
active_component: Cid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PatchView {
|
||||||
|
fn default() -> Self {
|
||||||
|
PatchView {
|
||||||
|
active_component: Cid::Patch(PatchCid::Activity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewPage for PatchView {
|
||||||
|
fn mount(
|
||||||
|
&self,
|
||||||
|
app: &mut Application<Cid, Message, NoUserEvent>,
|
||||||
|
context: &Context,
|
||||||
|
theme: &Theme,
|
||||||
|
) -> Result<()> {
|
||||||
|
if let Some((id, patch)) = context.patches.get(context.selected_patch) {
|
||||||
|
let navigation = widget::patch::navigation(theme).to_boxed();
|
||||||
|
let activity =
|
||||||
|
widget::patch::activity(theme, (*id, patch), &context.profile).to_boxed();
|
||||||
|
let files = widget::patch::files(theme, (*id, patch), &context.profile).to_boxed();
|
||||||
|
|
||||||
|
app.remount(
|
||||||
|
Cid::Patch(PatchCid::Navigation),
|
||||||
|
navigation,
|
||||||
|
subscription::navigation(),
|
||||||
|
)?;
|
||||||
|
app.remount(Cid::Patch(PatchCid::Activity), activity, vec![])?;
|
||||||
|
app.remount(Cid::Patch(PatchCid::Files), files, vec![])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, message: Message) {
|
||||||
|
if let Message::NavigationChanged(index) = message {
|
||||||
|
self.active_component = match index {
|
||||||
|
0 => Cid::Patch(PatchCid::Activity),
|
||||||
|
1 => Cid::Patch(PatchCid::Files),
|
||||||
|
_ => Cid::Patch(PatchCid::Activity),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&mut self, app: &mut Application<Cid, Message, NoUserEvent>, frame: &mut Frame) {
|
||||||
|
let area = frame.size();
|
||||||
|
let navigation_h = 2u16;
|
||||||
|
|
||||||
|
let layout = layout::default_page(area, navigation_h);
|
||||||
|
|
||||||
|
app.view(&Cid::Patch(PatchCid::Navigation), frame, layout[0]);
|
||||||
|
app.view(&self.active_component, frame, layout[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&self, app: &mut Application<Cid, Message, NoUserEvent>) -> Result<()> {
|
||||||
|
app.active(&self.active_component)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ use tuirealm::Frame;
|
||||||
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
|
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
|
||||||
|
|
||||||
pub mod cob;
|
pub mod cob;
|
||||||
pub mod subs;
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
/// Trait that must be implemented by client applications in order to be run
|
/// Trait that must be implemented by client applications in order to be run
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue