Skip to content

Commit

Permalink
Refactoring cli
Browse files Browse the repository at this point in the history
  • Loading branch information
bjohnson5 committed Jul 23, 2024
1 parent 6517229 commit d7b2aa0
Show file tree
Hide file tree
Showing 7 changed files with 1,010 additions and 848 deletions.
40 changes: 40 additions & 0 deletions blast_cli/src/blast_cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::shared::*;
use crate::new::*;
use crate::load::*;
use crate::configure::*;
use crate::run::*;

pub struct BlastCli {
pub new: NewTab,
pub load: LoadTab,
pub config: ConfigureTab,
pub run: RunTab,
}

impl BlastCli {
pub fn new() -> Self {
// TODO: this is a placeholder, initialize with the actual available models
let mut model_list: Vec<Model> = Vec::new();
model_list.push(Model{name: String::from("blast_lnd"), num_nodes: 0});
model_list.push(Model{name: String::from("blast_ldk"), num_nodes: 0});
model_list.push(Model{name: String::from("blast_cln"), num_nodes: 0});

// TODO: this is a placeholder, initialize with the actual saved simulations
let mut sim_list: Vec<String> = Vec::new();
sim_list.push(String::from("Test Simulation 1"));
sim_list.push(String::from("Another Test Simulation"));
sim_list.push(String::from("Simulation3"));

let nt = NewTab{models: StatefulList::with_items(model_list)};
let lt = LoadTab{sims: StatefulList::with_items(sim_list)};
let ct = ConfigureTab::new();
let rt = RunTab::new();

Self {
new: nt,
load: lt,
config: ct,
run: rt,
}
}
}
325 changes: 325 additions & 0 deletions blast_cli/src/configure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
use ratatui::{
crossterm::{
event::{KeyCode, KeyEvent, KeyEventKind}
},
prelude::*,
widgets::*,
};

use crate::shared::*;

#[derive(PartialEq,Clone)]
pub enum ConfigureSection {
Command,
Events,
Channels,
Activity
}

pub struct ConfigureTab {
pub input: String,
pub character_index: usize,
pub messages: Vec<String>,
pub events: StatefulList<String>,
pub channels: StatefulList<String>,
pub activity: StatefulList<String>,
pub current_section: ConfigureSection
}

impl ConfigureTab {
pub fn new() -> Self {
// TODO: this is a placeholder, initialize with the actual saved simulations
let mut events_list: Vec<String> = Vec::new();
let mut channel_list: Vec<String> = Vec::new();
let mut activity_list: Vec<String> = Vec::new();
events_list.push(String::from("10s OpenChannel (blast_lnd0000 --> blast_lnd0001: 2000msat)"));
channel_list.push(String::from("0: blast_lnd0000 --> blast_lnd0001: 2000msat"));
activity_list.push(String::from("blast_ldk0000 --> blast_lnd0004: 2000msat, 5s"));

Self {
input: String::new(),
messages: Vec::new(),
character_index: 0,
events: StatefulList::with_items(events_list),
channels: StatefulList::with_items(channel_list),
activity: StatefulList::with_items(activity_list),
current_section: ConfigureSection::Command
}
}

pub fn move_cursor_left(&mut self) {
let cursor_moved_left = self.character_index.saturating_sub(1);
self.character_index = self.clamp_cursor(cursor_moved_left);
}

pub fn move_cursor_right(&mut self) {
let cursor_moved_right = self.character_index.saturating_add(1);
self.character_index = self.clamp_cursor(cursor_moved_right);
}

pub fn enter_char(&mut self, new_char: char) {
let index = self.byte_index();
self.input.insert(index, new_char);
self.move_cursor_right();
}

pub fn byte_index(&mut self) -> usize {
self.input
.char_indices()
.map(|(i, _)| i)
.nth(self.character_index)
.unwrap_or(self.input.len())
}

pub fn delete_char(&mut self) {
let is_not_cursor_leftmost = self.character_index != 0;
if is_not_cursor_leftmost {
let current_index = self.character_index;
let from_left_to_current_index = current_index - 1;
let before_char_to_delete = self.input.chars().take(from_left_to_current_index);
let after_char_to_delete = self.input.chars().skip(current_index);
self.input = before_char_to_delete.chain(after_char_to_delete).collect();
self.move_cursor_left();
}
}

pub fn clamp_cursor(&self, new_cursor_pos: usize) -> usize {
new_cursor_pos.clamp(0, self.input.chars().count())
}

pub fn reset_cursor(&mut self) {
self.character_index = 0;
}
}

impl BlastTab for ConfigureTab {
fn draw(&mut self, frame: &mut Frame, area: Rect) {
let l = Layout::new(
Direction::Vertical,
[Constraint::Percentage(5), Constraint::Percentage(95)],
)
.split(area);

let layout1 = Layout::new(
Direction::Horizontal,
[Constraint::Percentage(50), Constraint::Percentage(50)],
)
.split(l[1]);

let layout = Layout::new(
Direction::Vertical,
[Constraint::Percentage(33), Constraint::Percentage(33), Constraint::Percentage(33)],
).split(layout1[1]);

let e: Vec<ListItem> = self.events.items.clone().iter()
.map(|i| ListItem::new(vec![text::Line::from(Span::raw(i.clone()))])).collect();
let etasks = List::new(e)
.block(Block::bordered().title("Events"))
.highlight_style(Style::default().fg(Color::LightYellow).add_modifier(Modifier::BOLD))
.highlight_symbol("> ");
frame.render_stateful_widget(etasks, layout[0], &mut self.events.state);

let c: Vec<ListItem> = self.channels.items.clone().iter()
.map(|i| ListItem::new(vec![text::Line::from(Span::raw(i.clone()))])).collect();
let ctasks = List::new(c)
.block(Block::bordered().title("Channels"))
.highlight_style(Style::default().fg(Color::LightYellow).add_modifier(Modifier::BOLD))
.highlight_symbol("> ");
frame.render_stateful_widget(ctasks, layout[1], &mut self.channels.state);

let a: Vec<ListItem> = self.activity.items.clone().iter()
.map(|i| ListItem::new(vec![text::Line::from(Span::raw(i.clone()))])).collect();
let atasks = List::new(a)
.block(Block::bordered().title("Activity"))
.highlight_style(Style::default().fg(Color::LightYellow).add_modifier(Modifier::BOLD))
.highlight_symbol("> ");
frame.render_stateful_widget(atasks, layout[2], &mut self.activity.state);

let vertical = Layout::vertical([
Constraint::Length(3),
Constraint::Min(1),
]);
let [input_area, messages_area] = vertical.areas(layout1[0]);

let msg = vec![
"Use ".into(),
"stop".bold(),
" command to stop network, ".into(),
"start".bold(),
" command to start simulation, ".into(),
"Tab".bold(),
" to change sections".into()
];
let text = Text::from(Line::from(msg)).patch_style(Style::default());
let help_message = Paragraph::new(text);
frame.render_widget(help_message, l[0]);

let input = Paragraph::new(self.input.as_str())
.style(match self.current_section {
ConfigureSection::Command => Style::default().fg(Color::LightYellow),
_ => Style::default(),
})
.block(Block::bordered().title("Command"));
frame.render_widget(input, input_area);

match self.current_section {
ConfigureSection::Command => {
#[allow(clippy::cast_possible_truncation)]
frame.set_cursor(
input_area.x + self.character_index as u16 + 1,
input_area.y + 1,
);
}
_ => {}
}

let messages: Vec<ListItem> = self
.messages
.iter()
.enumerate()
.map(|(i, m)| {
let content = Line::from(Span::raw(format!("{i}: {m}")));
ListItem::new(content)
})
.collect();
let messages = List::new(messages).block(Block::bordered().title("Output"));
frame.render_widget(messages, messages_area);
}

fn init(&mut self) {
self.current_section = ConfigureSection::Command;
}

fn close(&mut self) {
self.messages.clear();
self.input.clear();
self.events.clear();
self.channels.clear();
self.activity.clear();
self.reset_cursor();
}

fn is_new(&self) -> bool {
false
}

fn is_load(&self) -> bool {
false
}

fn process(&mut self, key: KeyEvent) -> ProcessResult {
if key.kind == KeyEventKind::Press {
match key.code {
// If Enter is pressed while on the Config page, execute the current command
KeyCode::Enter => {
if self.current_section != ConfigureSection::Command {
return ProcessResult::NoOp;
}
let command = self.input.clone();
if command == "stop" {
self.close();
return ProcessResult::StopNetwork;
} else if command == "start" {
self.close();
return ProcessResult::StartSim;
} else {
// Otherwise, run the command and show the output
// TODO: execute the command and get the output
self.messages.clear();
self.input.clear();
self.reset_cursor();
self.messages.push(command.clone());
}
}
KeyCode::Char(to_insert) => {
if self.current_section != ConfigureSection::Command {
return ProcessResult::NoOp;
}
self.enter_char(to_insert);
}
KeyCode::Backspace => {
if self.current_section != ConfigureSection::Command {
return ProcessResult::NoOp;
}
self.delete_char();
}
KeyCode::Left => {
if self.current_section != ConfigureSection::Command {
return ProcessResult::NoOp;
}
self.move_cursor_left();
}
KeyCode::Right => {
if self.current_section != ConfigureSection::Command {
return ProcessResult::NoOp;
}
self.move_cursor_right();
}
KeyCode::Tab => {
match self.current_section {
ConfigureSection::Command => {
self.current_section = ConfigureSection::Events;
self.input.clear();
self.reset_cursor();
self.events.next();
}
ConfigureSection::Events => {
self.current_section = ConfigureSection::Channels;
self.events.clear();
self.channels.next();
}
ConfigureSection::Channels => {
self.current_section = ConfigureSection::Activity;
self.channels.clear();
self.activity.next();
}
ConfigureSection::Activity => {
self.current_section = ConfigureSection::Command;
self.activity.clear();
}
}
}
KeyCode::Up => {
match self.current_section {
ConfigureSection::Command => {}
ConfigureSection::Events => {
self.events.previous();
}
ConfigureSection::Channels => {
self.channels.previous();
}
ConfigureSection::Activity => {
self.activity.previous();
}
}
}
KeyCode::Down => {
match self.current_section {
ConfigureSection::Command => {}
ConfigureSection::Events => {
self.events.next();
}
ConfigureSection::Channels => {
self.channels.next();
}
ConfigureSection::Activity => {
self.activity.next();
}
}
}
_ => {}
}
}

return ProcessResult::NoOp;
}

fn get_index(&self) -> usize {
2
}

fn update_data(&mut self) {
// TODO: update data from blast core
return;
}
}
Loading

0 comments on commit d7b2aa0

Please sign in to comment.