forked from NationTech/harmony
		
	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)) | ||||||
|             output_area, |             .state(&self.tui_state); | ||||||
|         ) |         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)] | ||||||
| @ -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