feat(widget): add help widget and improve score widget
- Introduced a new `Help` widget to display user instructions. - Improved the `ScoreListWidget` by removing unnecessary execution rendering methods and simplifying state transitions. - Cleaned up unused imports and refactored code for better readability.
This commit is contained in:
parent
6628e193e0
commit
f1f2c796c4
@ -31,7 +31,7 @@ impl std::fmt::Display for InterpretName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Interpret: std::fmt::Debug {
|
pub trait Interpret: std::fmt::Debug + Send {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
|
|||||||
@ -35,7 +35,7 @@ impl Maestro {
|
|||||||
score_mut.append(&mut scores);
|
score_mut.append(&mut scores);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn interpret<S: Score>(&self, score: &S) -> Result<Outcome, InterpretError> {
|
pub async fn interpret(&self, score: Box<dyn Score>) -> Result<Outcome, InterpretError> {
|
||||||
info!("Running score {score:?}");
|
info!("Running score {score:?}");
|
||||||
let interpret = score.create_interpret();
|
let interpret = score.create_interpret();
|
||||||
info!("Launching interpret {interpret:?}");
|
info!("Launching interpret {interpret:?}");
|
||||||
|
|||||||
@ -52,7 +52,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync> {
|
pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync + Send> {
|
||||||
pub score: K8sResourceScore<K>,
|
pub score: K8sResourceScore<K>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ impl<
|
|||||||
+ DeserializeOwned
|
+ DeserializeOwned
|
||||||
+ serde::Serialize
|
+ serde::Serialize
|
||||||
+ Default
|
+ Default
|
||||||
|
+ Send
|
||||||
+ Sync,
|
+ Sync,
|
||||||
> Interpret for K8sResourceInterpret<K>
|
> Interpret for K8sResourceInterpret<K>
|
||||||
where
|
where
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
mod ratatui_utils;
|
mod ratatui_utils;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, info};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::StreamExt;
|
use tokio_stream::StreamExt;
|
||||||
use widget::score::ScoreListWidget;
|
use tui_logger::{TuiWidgetEvent, TuiWidgetState};
|
||||||
|
use widget::{help::HelpWidget, score::ScoreListWidget};
|
||||||
|
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
@ -48,10 +49,10 @@ pub async fn init(maestro: Maestro) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct HarmonyTUI {
|
pub struct HarmonyTUI {
|
||||||
maestro: Maestro,
|
|
||||||
score: ScoreListWidget,
|
score: ScoreListWidget,
|
||||||
should_quit: bool,
|
should_quit: bool,
|
||||||
channel_handle: tokio::task::JoinHandle<()>,
|
channel_handle: tokio::task::JoinHandle<()>,
|
||||||
|
tui_state: TuiWidgetState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -61,23 +62,34 @@ enum HarmonyTuiEvent {
|
|||||||
|
|
||||||
impl HarmonyTUI {
|
impl HarmonyTUI {
|
||||||
pub fn new(maestro: Maestro) -> Self {
|
pub fn new(maestro: Maestro) -> Self {
|
||||||
let (handle, sender) = Self::start_channel();
|
let maestro = Arc::new(maestro);
|
||||||
|
let (handle, sender) = Self::start_channel(maestro.clone());
|
||||||
let score = ScoreListWidget::new(Self::scores_list(&maestro), sender);
|
let score = ScoreListWidget::new(Self::scores_list(&maestro), sender);
|
||||||
|
|
||||||
HarmonyTUI {
|
HarmonyTUI {
|
||||||
maestro,
|
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
score,
|
score,
|
||||||
channel_handle: handle,
|
channel_handle: handle,
|
||||||
|
tui_state: TuiWidgetState::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_channel() -> (tokio::task::JoinHandle<()>, mpsc::Sender<HarmonyTuiEvent>) {
|
fn start_channel(
|
||||||
|
maestro: Arc<Maestro>,
|
||||||
|
) -> (tokio::task::JoinHandle<()>, mpsc::Sender<HarmonyTuiEvent>) {
|
||||||
let (sender, mut receiver) = mpsc::channel::<HarmonyTuiEvent>(32);
|
let (sender, mut receiver) = mpsc::channel::<HarmonyTuiEvent>(32);
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
info!("Starting message channel receiver loop");
|
info!("Starting message channel receiver loop");
|
||||||
while let Some(event) = receiver.recv().await {
|
while let Some(event) = receiver.recv().await {
|
||||||
info!("Received event {event:#?}");
|
info!("Received event {event:#?}");
|
||||||
|
match event {
|
||||||
|
HarmonyTuiEvent::LaunchScore(score_item) => {
|
||||||
|
info!(
|
||||||
|
"Interpretation result {:#?}",
|
||||||
|
maestro.interpret(score_item.0).await
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
info!("STOPPING message channel receiver loop");
|
info!("STOPPING message channel receiver loop");
|
||||||
});
|
});
|
||||||
@ -86,9 +98,9 @@ impl HarmonyTUI {
|
|||||||
|
|
||||||
pub async fn init(mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn init(mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// Set max_log_level to Trace
|
// Set max_log_level to Trace
|
||||||
tui_logger::init_logger(log::LevelFilter::Trace).unwrap();
|
tui_logger::init_logger(log::LevelFilter::Info).unwrap();
|
||||||
// Set default level for unknown targets to Trace
|
// Set default level for unknown targets to Trace
|
||||||
tui_logger::set_default_level(log::LevelFilter::Trace);
|
tui_logger::set_default_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
let mut terminal = ratatui::init();
|
let mut terminal = ratatui::init();
|
||||||
@ -116,22 +128,30 @@ impl HarmonyTUI {
|
|||||||
let size = frame.area();
|
let size = frame.area();
|
||||||
frame.set_cursor_position(Position::new(size.x / 2, size.y / 2));
|
frame.set_cursor_position(Position::new(size.x / 2, size.y / 2));
|
||||||
|
|
||||||
|
let [app_area, help_area] =
|
||||||
|
Layout::vertical([Constraint::Percentage(100), Constraint::Min(4)])
|
||||||
|
.areas(frame.area());
|
||||||
|
|
||||||
|
let help_block = Block::default().borders(Borders::TOP);
|
||||||
|
frame.render_widget(&help_block, help_area);
|
||||||
|
frame.render_widget(HelpWidget::new(), help_block.inner(help_area));
|
||||||
|
|
||||||
let [list_area, output_area] =
|
let [list_area, output_area] =
|
||||||
Layout::horizontal([Constraint::Min(30), Constraint::Percentage(100)])
|
Layout::horizontal([Constraint::Min(30), Constraint::Percentage(100)])
|
||||||
.areas(frame.area());
|
.areas(app_area);
|
||||||
|
|
||||||
|
|
||||||
let block = Block::default().borders(Borders::RIGHT);
|
let block = Block::default().borders(Borders::RIGHT);
|
||||||
frame.render_widget(&block, list_area);
|
frame.render_widget(&block, list_area);
|
||||||
self.score.render(list_area, frame);
|
self.score.render(list_area, frame);
|
||||||
frame.render_widget(
|
let tui_logger = tui_logger::TuiLoggerWidget::default()
|
||||||
tui_logger::TuiLoggerWidget::default()
|
.style_error(Style::default().fg(Color::Red))
|
||||||
.style_error(Style::default().fg(Color::Red))
|
.style_warn(Style::default().fg(Color::LightRed))
|
||||||
.style_warn(Style::default().fg(Color::LightRed))
|
.style_info(Style::default().fg(Color::LightGreen))
|
||||||
.style_info(Style::default().fg(Color::LightGreen))
|
.style_debug(Style::default().fg(Color::Gray))
|
||||||
.style_debug(Style::default().fg(Color::Gray))
|
.style_trace(Style::default().fg(Color::Gray))
|
||||||
.style_trace(Style::default().fg(Color::Gray)),
|
.state(&self.tui_state);
|
||||||
output_area,
|
frame.render_widget(tui_logger, output_area)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scores_list(maestro: &Maestro) -> Vec<ScoreItem> {
|
fn scores_list(maestro: &Maestro) -> Vec<ScoreItem> {
|
||||||
@ -144,10 +164,14 @@ impl HarmonyTUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_event(&mut self, event: &Event) {
|
async fn handle_event(&mut self, event: &Event) {
|
||||||
|
debug!("Got event {event:?}");
|
||||||
if let Event::Key(key) = event {
|
if let Event::Key(key) = event {
|
||||||
if key.kind == KeyEventKind::Press {
|
if key.kind == KeyEventKind::Press {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
|
||||||
|
KeyCode::PageUp => self.tui_state.transition(TuiWidgetEvent::PrevPageKey),
|
||||||
|
KeyCode::PageDown => self.tui_state.transition(TuiWidgetEvent::NextPageKey),
|
||||||
|
KeyCode::Char('G') => self.tui_state.transition(TuiWidgetEvent::EscapeKey),
|
||||||
_ => self.score.handle_event(event).await,
|
_ => self.score.handle_event(event).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
harmony-rs/harmony_tui/src/widget/help.rs
Normal file
20
harmony-rs/harmony_tui/src/widget/help.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use ratatui::widgets::{Paragraph, Widget, Wrap};
|
||||||
|
|
||||||
|
pub(crate) struct HelpWidget;
|
||||||
|
impl HelpWidget {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for HelpWidget {
|
||||||
|
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized {
|
||||||
|
let text = Paragraph::new("Usage => q/Esc: Quit | j/↑ :Select UP | k/↓: Select Down | Enter: Launch Score | PageUp/PageDown: Scroll Logs | g/Home: Logs top | Shift+G/End: Logs bottom")
|
||||||
|
.centered()
|
||||||
|
.wrap(Wrap { trim: false });
|
||||||
|
|
||||||
|
Widget::render(text, area, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
pub mod score;
|
pub mod score;
|
||||||
|
pub mod help;
|
||||||
|
|||||||
@ -1,26 +1,22 @@
|
|||||||
use std::sync::{ Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{info, warn};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Constraint, Rect},
|
layout::Rect,
|
||||||
style::{Style, Stylize},
|
style::{Style, Stylize},
|
||||||
widgets::{Block, Clear, List, ListState, Paragraph, StatefulWidget, Widget},
|
widgets::{List, ListState, StatefulWidget, Widget},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::{HarmonyTuiEvent, ScoreItem};
|
use crate::{HarmonyTuiEvent, ScoreItem};
|
||||||
|
|
||||||
use crate::ratatui_utils::center;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ExecutionState {
|
enum ExecutionState {
|
||||||
INITIATED,
|
INITIATED,
|
||||||
CONFIRMED,
|
|
||||||
CANCELED,
|
|
||||||
RUNNING,
|
RUNNING,
|
||||||
COMPLETED,
|
CANCELED,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -39,7 +35,7 @@ pub(crate) struct ScoreListWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ScoreListWidget {
|
impl ScoreListWidget {
|
||||||
pub(crate) fn new(scores: Vec<ScoreItem>, sender : mpsc::Sender<HarmonyTuiEvent>) -> Self {
|
pub(crate) fn new(scores: Vec<ScoreItem>, sender: mpsc::Sender<HarmonyTuiEvent>) -> Self {
|
||||||
let mut list_state = ListState::default();
|
let mut list_state = ListState::default();
|
||||||
list_state.select_first();
|
list_state.select_first();
|
||||||
let list_state = Arc::new(RwLock::new(list_state));
|
let list_state = Arc::new(RwLock::new(list_state));
|
||||||
@ -80,16 +76,8 @@ impl ScoreListWidget {
|
|||||||
|
|
||||||
pub(crate) fn render(&self, area: Rect, frame: &mut Frame) {
|
pub(crate) fn render(&self, area: Rect, frame: &mut Frame) {
|
||||||
frame.render_widget(self, area);
|
frame.render_widget(self, area);
|
||||||
self.render_execution(frame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_execution(&self, frame: &mut Frame) {
|
|
||||||
if let None = self.execution {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let execution = self.execution.as_ref().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_execution(&mut self) {
|
fn clear_execution(&mut self) {
|
||||||
match self.execution.take() {
|
match self.execution.take() {
|
||||||
@ -104,13 +92,12 @@ impl ScoreListWidget {
|
|||||||
if let Some(execution) = &mut self.execution {
|
if let Some(execution) = &mut self.execution {
|
||||||
match confirm {
|
match confirm {
|
||||||
true => {
|
true => {
|
||||||
execution.state = ExecutionState::CONFIRMED;
|
execution.state = ExecutionState::RUNNING;
|
||||||
info!("Launch execution {:?}", execution);
|
info!("Launch execution {:?}", execution);
|
||||||
warn!("Launch execution");
|
self.sender
|
||||||
error!("Launch execution");
|
.send(HarmonyTuiEvent::LaunchScore(execution.score.clone()))
|
||||||
trace!("Launch execution");
|
.await
|
||||||
debug!("Launch execution");
|
.expect("Should be able to send message");
|
||||||
self.sender.send(HarmonyTuiEvent::LaunchScore(execution.score.clone())).await.expect("Should be able to send message");
|
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
execution.state = ExecutionState::CANCELED;
|
execution.state = ExecutionState::CANCELED;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user