This commit adds `serde` dependency and derives `Serialize` trait for `Score` types. This is necessary for serialization and deserialization of these types, which is required to display Scores to various user interfaces - Added `serde` dependency to `harmony_types/Cargo.toml`. - Added `serde::Serialize` derive macro to `MacAddress` in `harmony_types/src/lib.rs`. - Added `serde::Serialize` derive macro to `Config` in `opnsense-config/src/config/config.rs`. - Added `serde::Serialize` derive macro to `Score` in `harmony_types/src/lib.rs`. - Added `serde::Serialize` derive macro to `Config` and `Score` in relevant modules. - Added placeholder `todo!()` implementations for `serialize` methods. These will be implemented in future commits.
148 lines
4.5 KiB
Rust
148 lines
4.5 KiB
Rust
use std::sync::{Arc, RwLock};
|
|
|
|
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
|
use harmony::{score::Score, topology::Topology};
|
|
use log::{info, warn};
|
|
use ratatui::{
|
|
Frame,
|
|
layout::Rect,
|
|
style::{Style, Stylize},
|
|
widgets::{List, ListItem, ListState, StatefulWidget, Widget},
|
|
};
|
|
use tokio::sync::mpsc;
|
|
|
|
use crate::HarmonyTuiEvent;
|
|
|
|
#[derive(Debug)]
|
|
enum ExecutionState {
|
|
INITIATED,
|
|
RUNNING,
|
|
CANCELED,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Execution<T: Topology> {
|
|
state: ExecutionState,
|
|
score: Box<dyn Score<T>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct ScoreListWidget<T: Topology> {
|
|
list_state: Arc<RwLock<ListState>>,
|
|
scores: Vec<Box<dyn Score<T>>>,
|
|
execution: Option<Execution<T>>,
|
|
execution_history: Vec<Execution<T>>,
|
|
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
|
|
}
|
|
|
|
impl<T: Topology + std::fmt::Debug> ScoreListWidget<T> {
|
|
pub(crate) fn new(
|
|
scores: Vec<Box<dyn Score<T>>>,
|
|
sender: mpsc::Sender<HarmonyTuiEvent<T>>,
|
|
) -> Self {
|
|
let mut list_state = ListState::default();
|
|
list_state.select_first();
|
|
let list_state = Arc::new(RwLock::new(list_state));
|
|
Self {
|
|
scores,
|
|
list_state,
|
|
execution: None,
|
|
execution_history: vec![],
|
|
sender,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn launch_execution(&mut self) {
|
|
let list_read = self.list_state.read().unwrap();
|
|
if let Some(index) = list_read.selected() {
|
|
let score = self
|
|
.scores
|
|
.get(index)
|
|
.expect("List state should always match with internal Vec");
|
|
|
|
self.execution = Some(Execution {
|
|
state: ExecutionState::INITIATED,
|
|
score: score.clone_box(),
|
|
});
|
|
info!("{:#?}\n\nConfirm Execution (Press y/n)", score);
|
|
} else {
|
|
warn!("No Score selected, nothing to launch");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn scroll_down(&self) {
|
|
self.list_state.write().unwrap().scroll_down_by(1);
|
|
}
|
|
|
|
pub(crate) fn scroll_up(&self) {
|
|
self.list_state.write().unwrap().scroll_up_by(1);
|
|
}
|
|
|
|
pub(crate) fn render(&self, area: Rect, frame: &mut Frame) {
|
|
frame.render_widget(self, area);
|
|
}
|
|
|
|
fn clear_execution(&mut self) {
|
|
match self.execution.take() {
|
|
Some(execution) => {
|
|
self.execution_history.push(execution);
|
|
}
|
|
None => warn!("Should never clear execution when no execution exists"),
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn confirm(&mut self, confirm: bool) {
|
|
if let Some(execution) = &mut self.execution {
|
|
match confirm {
|
|
true => {
|
|
execution.state = ExecutionState::RUNNING;
|
|
info!("Launch execution {:?}", execution);
|
|
self.sender
|
|
.send(HarmonyTuiEvent::LaunchScore(execution.score.clone_box()))
|
|
.await
|
|
.expect("Should be able to send message");
|
|
}
|
|
false => {
|
|
execution.state = ExecutionState::CANCELED;
|
|
info!("Execution cancelled");
|
|
self.clear_execution();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) async fn handle_event(&mut self, event: &Event) {
|
|
if let Event::Key(key) = event {
|
|
if key.kind == KeyEventKind::Press {
|
|
match key.code {
|
|
KeyCode::Char('j') | KeyCode::Down => self.scroll_down(),
|
|
KeyCode::Char('k') | KeyCode::Up => self.scroll_up(),
|
|
KeyCode::Enter => self.launch_execution(),
|
|
KeyCode::Char('y') => self.confirm(true).await,
|
|
KeyCode::Char('n') => self.confirm(false).await,
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Topology> Widget for &ScoreListWidget<T> {
|
|
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let mut list_state = self.list_state.write().unwrap();
|
|
let scores_items: Vec<ListItem<'_>> = self.scores.iter().map(score_to_list_item).collect();
|
|
let list = List::new(scores_items)
|
|
.highlight_style(Style::new().bold().italic())
|
|
.highlight_symbol("🠊 ");
|
|
|
|
StatefulWidget::render(list, area, buf, &mut list_state)
|
|
}
|
|
}
|
|
|
|
fn score_to_list_item<'a, T: Topology>(score: &'a Box<dyn Score<T>>) -> ListItem<'a> {
|
|
ListItem::new(score.name())
|
|
}
|