parent
90779527cf
commit
3833ba86db
8 changed files with 127 additions and 29 deletions
|
@ -82,11 +82,12 @@ standardwebhooks = "1.0"
|
||||||
|
|
||||||
# boilerplate
|
# boilerplate
|
||||||
bon = "3.0"
|
bon = "3.0"
|
||||||
derive_more = { version = "1.0.0-beta", features = [
|
derive_more = { version = "1.0.0", features = [
|
||||||
"as_ref",
|
"as_ref",
|
||||||
"constructor",
|
"constructor",
|
||||||
"display",
|
"display",
|
||||||
"deref",
|
"deref",
|
||||||
|
"deref_mut",
|
||||||
"from",
|
"from",
|
||||||
] }
|
] }
|
||||||
derive-with = "0.5"
|
derive-with = "0.5"
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub use model::*;
|
||||||
|
|
||||||
use crate::tell;
|
use crate::tell;
|
||||||
|
|
||||||
use super::Tick;
|
use super::{components::key_focus::KeyFocus, Tick};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
|
@ -92,7 +92,8 @@ impl Tui {
|
||||||
if key.kind != KeyEventKind::Press {
|
if key.kind != KeyEventKind::Press {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match key.code {
|
match self.state.key_focus {
|
||||||
|
KeyFocus::None => match key.code {
|
||||||
KeyCode::Char('q') => {
|
KeyCode::Char('q') => {
|
||||||
actor_tui.kill();
|
actor_tui.kill();
|
||||||
}
|
}
|
||||||
|
@ -110,7 +111,23 @@ impl Tui {
|
||||||
KeyCode::Char('G') | KeyCode::End => {
|
KeyCode::Char('G') | KeyCode::End => {
|
||||||
self.scroll_view_state.scroll_to_bottom();
|
self.scroll_view_state.scroll_to_bottom();
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('/') => {
|
||||||
|
self.state.key_focus = KeyFocus::Filter;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
},
|
||||||
|
KeyFocus::Filter => match key.code {
|
||||||
|
KeyCode::Char(char) => self.state.filter.push(char),
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
self.state.filter.pop();
|
||||||
|
}
|
||||||
|
KeyCode::Tab | KeyCode::Enter => self.state.key_focus = KeyFocus::None,
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.state.filter = UIRepoFilter::default();
|
||||||
|
self.state.key_focus = KeyFocus::None;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
//
|
//
|
||||||
use std::{collections::BTreeMap, fmt::Display, time::Instant};
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
use derive_more::derive::{DerefMut, Display};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::Alignment,
|
layout::Alignment,
|
||||||
prelude::{Buffer, Rect},
|
prelude::{Buffer, Rect},
|
||||||
|
@ -14,16 +19,21 @@ use tui_scrollview::ScrollViewState;
|
||||||
|
|
||||||
use git_next_core::{
|
use git_next_core::{
|
||||||
git::{self, graph::Log, Commit},
|
git::{self, graph::Log, Commit},
|
||||||
ForgeAlias, RepoAlias, RepoBranches,
|
newtype, s, ForgeAlias, RepoAlias, RepoBranches,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{server::actor::messages::ValidAppConfig, tui::components::ConfiguredAppWidget};
|
use crate::{
|
||||||
|
server::actor::messages::ValidAppConfig,
|
||||||
|
tui::components::{key_focus::KeyFocus, ConfiguredAppWidget},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
started: Instant,
|
started: Instant,
|
||||||
pub mode: ServerState,
|
pub mode: ServerState,
|
||||||
|
pub filter: UIRepoFilter,
|
||||||
|
pub key_focus: KeyFocus,
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
pub fn initial() -> Self {
|
pub fn initial() -> Self {
|
||||||
|
@ -31,6 +41,8 @@ impl State {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
started: Instant::now(),
|
started: Instant::now(),
|
||||||
mode: ServerState::Initial { tick: 0 },
|
mode: ServerState::Initial { tick: 0 },
|
||||||
|
filter: UIRepoFilter::default(),
|
||||||
|
key_focus: KeyFocus::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +67,15 @@ fn time() -> String {
|
||||||
chrono::Local::now().format("%H:%M").to_string()
|
chrono::Local::now().format("%H:%M").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newtype!(
|
||||||
|
UIRepoFilter,
|
||||||
|
String,
|
||||||
|
Default,
|
||||||
|
DerefMut,
|
||||||
|
Display,
|
||||||
|
"Filter to limit repos shown"
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum ServerState {
|
pub enum ServerState {
|
||||||
/// UI has started but has no information on the state of the server
|
/// UI has started but has no information on the state of the server
|
||||||
|
@ -232,6 +253,14 @@ pub enum RepoState {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
impl RepoState {
|
impl RepoState {
|
||||||
|
pub fn repo_alias(&self) -> &RepoAlias {
|
||||||
|
match self {
|
||||||
|
RepoState::Identified { repo_alias, .. }
|
||||||
|
| RepoState::Configured { repo_alias, .. }
|
||||||
|
| RepoState::Ready { repo_alias, .. } => repo_alias,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn update_branches(&mut self, branches: RepoBranches) {
|
pub fn update_branches(&mut self, branches: RepoBranches) {
|
||||||
match self {
|
match self {
|
||||||
|
@ -359,13 +388,36 @@ impl StatefulWidget for &State {
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
let block = Block::bordered()
|
let block = Block::bordered()
|
||||||
|
.title_top(
|
||||||
|
Line::from(
|
||||||
|
if self.filter.is_empty() && self.key_focus == KeyFocus::None {
|
||||||
|
s!("")
|
||||||
|
} else {
|
||||||
|
format!(" Filter: {} ", self.filter)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
.style(if self.key_focus == KeyFocus::Filter {
|
||||||
|
Color::Red
|
||||||
|
} else {
|
||||||
|
Color::Green
|
||||||
|
}),
|
||||||
|
)
|
||||||
.title_top(
|
.title_top(
|
||||||
Line::from(format!(" Git-Next v{} ", clap::crate_version!()).bold())
|
Line::from(format!(" Git-Next v{} ", clap::crate_version!()).bold())
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
)
|
)
|
||||||
|
.title_bottom(
|
||||||
|
Line::from(if self.key_focus == KeyFocus::Filter {
|
||||||
|
s!(" [esc] clear [tab/enter] finish ")
|
||||||
|
} else {
|
||||||
|
s!("")
|
||||||
|
})
|
||||||
|
.alignment(Alignment::Left),
|
||||||
|
)
|
||||||
.title_bottom(
|
.title_bottom(
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
" [q]uit ".into(),
|
" [q]uit [/] filter ".into(),
|
||||||
self.beating_heart().into(),
|
self.beating_heart().into(),
|
||||||
" ".into(),
|
" ".into(),
|
||||||
])
|
])
|
||||||
|
@ -380,7 +432,11 @@ impl StatefulWidget for &State {
|
||||||
.centered()
|
.centered()
|
||||||
.render(interior, buf),
|
.render(interior, buf),
|
||||||
ServerState::Configured { forges } => {
|
ServerState::Configured { forges } => {
|
||||||
ConfiguredAppWidget { forges }.render(interior, buf, state);
|
ConfiguredAppWidget {
|
||||||
|
forges,
|
||||||
|
filter: &self.filter,
|
||||||
|
}
|
||||||
|
.render(interior, buf, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
use tui_scrollview::{ScrollView, ScrollViewState};
|
use tui_scrollview::{ScrollView, ScrollViewState};
|
||||||
|
|
||||||
use crate::tui::actor::ForgeState;
|
use crate::tui::actor::{ForgeState, UIRepoFilter};
|
||||||
|
|
||||||
use super::{forge::ForgeWidget, HeightContraintLength};
|
use super::{forge::ForgeWidget, HeightContraintLength};
|
||||||
|
|
||||||
pub struct ConfiguredAppWidget<'a> {
|
pub struct ConfiguredAppWidget<'a> {
|
||||||
pub forges: &'a BTreeMap<ForgeAlias, ForgeState>,
|
pub forges: &'a BTreeMap<ForgeAlias, ForgeState>,
|
||||||
|
pub filter: &'a UIRepoFilter,
|
||||||
}
|
}
|
||||||
impl HeightContraintLength for ConfiguredAppWidget<'_> {
|
impl HeightContraintLength for ConfiguredAppWidget<'_> {
|
||||||
fn height_constraint_length(&self) -> u16 {
|
fn height_constraint_length(&self) -> u16 {
|
||||||
|
@ -62,6 +63,7 @@ impl<'a> ConfiguredAppWidget<'a> {
|
||||||
forge_alias,
|
forge_alias,
|
||||||
repos: &state.repos,
|
repos: &state.repos,
|
||||||
view_state: state.view_state,
|
view_state: state.view_state,
|
||||||
|
filter: self.filter,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,14 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
actor::RepoState,
|
actor::{RepoState, UIRepoFilter},
|
||||||
components::{repo::RepoWidget, HeightContraintLength},
|
components::{repo::RepoWidget, HeightContraintLength},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ExpandedForgeWidget<'a> {
|
pub struct ExpandedForgeWidget<'a> {
|
||||||
pub forge_alias: &'a ForgeAlias,
|
pub forge_alias: &'a ForgeAlias,
|
||||||
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
||||||
|
pub filter: &'a UIRepoFilter,
|
||||||
}
|
}
|
||||||
impl HeightContraintLength for ExpandedForgeWidget<'_> {
|
impl HeightContraintLength for ExpandedForgeWidget<'_> {
|
||||||
fn height_constraint_length(&self) -> u16 {
|
fn height_constraint_length(&self) -> u16 {
|
||||||
|
@ -55,6 +56,15 @@ impl<'a> ExpandedForgeWidget<'a> {
|
||||||
fn children(&self) -> Vec<RepoWidget<'a>> {
|
fn children(&self) -> Vec<RepoWidget<'a>> {
|
||||||
self.repos
|
self.repos
|
||||||
.values()
|
.values()
|
||||||
|
.filter(|repo_state| {
|
||||||
|
if self.filter.is_empty() {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let repo_alias = repo_state.repo_alias();
|
||||||
|
// eprintln!("--> {} ? {}", self.filter.as_str(), repo_alias);
|
||||||
|
repo_alias.contains(self.filter.as_str())
|
||||||
|
}
|
||||||
|
})
|
||||||
.map(|repo_state| RepoWidget { repo_state })
|
.map(|repo_state| RepoWidget { repo_state })
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use expanded::ExpandedForgeWidget;
|
||||||
use git_next_core::{ForgeAlias, RepoAlias};
|
use git_next_core::{ForgeAlias, RepoAlias};
|
||||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||||
|
|
||||||
use crate::tui::actor::{RepoState, ViewState};
|
use crate::tui::actor::{RepoState, UIRepoFilter, ViewState};
|
||||||
|
|
||||||
use super::HeightContraintLength;
|
use super::HeightContraintLength;
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub struct ForgeWidget<'a> {
|
||||||
pub forge_alias: &'a ForgeAlias,
|
pub forge_alias: &'a ForgeAlias,
|
||||||
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
pub repos: &'a BTreeMap<RepoAlias, RepoState>,
|
||||||
pub view_state: ViewState,
|
pub view_state: ViewState,
|
||||||
|
pub filter: &'a UIRepoFilter,
|
||||||
}
|
}
|
||||||
impl HeightContraintLength for ForgeWidget<'_> {
|
impl HeightContraintLength for ForgeWidget<'_> {
|
||||||
fn height_constraint_length(&self) -> u16 {
|
fn height_constraint_length(&self) -> u16 {
|
||||||
|
@ -28,6 +29,7 @@ impl HeightContraintLength for ForgeWidget<'_> {
|
||||||
ViewState::Expanded => ExpandedForgeWidget {
|
ViewState::Expanded => ExpandedForgeWidget {
|
||||||
forge_alias: self.forge_alias,
|
forge_alias: self.forge_alias,
|
||||||
repos: self.repos,
|
repos: self.repos,
|
||||||
|
filter: self.filter,
|
||||||
}
|
}
|
||||||
.height_constraint_length(),
|
.height_constraint_length(),
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,7 @@ impl Widget for ForgeWidget<'_> {
|
||||||
ViewState::Expanded => ExpandedForgeWidget {
|
ViewState::Expanded => ExpandedForgeWidget {
|
||||||
forge_alias: self.forge_alias,
|
forge_alias: self.forge_alias,
|
||||||
repos: self.repos,
|
repos: self.repos,
|
||||||
|
filter: self.filter,
|
||||||
}
|
}
|
||||||
.render(area, buf),
|
.render(area, buf),
|
||||||
}
|
}
|
||||||
|
|
8
crates/cli/src/tui/components/key_focus.rs
Normal file
8
crates/cli/src/tui/components/key_focus.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum KeyFocus {
|
||||||
|
/// Keyboard is not focused on any input
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Keyboard is focused on editing the filter
|
||||||
|
Filter,
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
mod configured_app;
|
mod configured_app;
|
||||||
mod forge;
|
mod forge;
|
||||||
mod history;
|
mod history;
|
||||||
|
pub mod key_focus;
|
||||||
mod repo;
|
mod repo;
|
||||||
|
|
||||||
pub use configured_app::ConfiguredAppWidget;
|
pub use configured_app::ConfiguredAppWidget;
|
||||||
|
|
Loading…
Add table
Reference in a new issue