move frontend stuff to frontend module, really nicely shutdown
This commit is contained in:
parent
63403faf4d
commit
dd52430508
3 changed files with 158 additions and 142 deletions
138
src/frontend/mod.rs
Normal file
138
src/frontend/mod.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
use std::{collections::BTreeMap, io, net::SocketAddr};
|
||||||
|
|
||||||
|
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use joecalsend::JoecalState;
|
||||||
|
use ratatui::{
|
||||||
|
DefaultTerminal,
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::Rect,
|
||||||
|
style::Stylize,
|
||||||
|
symbols::border,
|
||||||
|
text::{Line, Text},
|
||||||
|
widgets::{Block, Paragraph, Widget},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
pub state: JoecalState,
|
||||||
|
pub screen: CurrentScreen,
|
||||||
|
pub events: EventStream,
|
||||||
|
// addr -> (alias, fingerprint)
|
||||||
|
pub peers: Peers,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum CurrentScreen {
|
||||||
|
Main,
|
||||||
|
Sending,
|
||||||
|
Receiving,
|
||||||
|
Stopping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn new(state: JoecalState) -> Self {
|
||||||
|
App {
|
||||||
|
state,
|
||||||
|
screen: CurrentScreen::Main,
|
||||||
|
peers: Default::default(),
|
||||||
|
events: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||||
|
loop {
|
||||||
|
terminal.draw(|frame| self.draw(frame))?;
|
||||||
|
self.handle_events().await?;
|
||||||
|
|
||||||
|
if self.screen == CurrentScreen::Stopping {
|
||||||
|
self.state.stop().await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let peers = self.state.peers.lock().await;
|
||||||
|
self.peers.clear();
|
||||||
|
peers.iter().for_each(|(k, v)| {
|
||||||
|
// k is fingerprint, v is addr, device
|
||||||
|
let addr = v.0;
|
||||||
|
let alias = v.1.alias.clone();
|
||||||
|
let fingerprint = k.clone();
|
||||||
|
self.peers.insert(addr, (alias, fingerprint));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_events(&mut self) -> io::Result<()> {
|
||||||
|
tokio::select! {
|
||||||
|
event = self.events.next().fuse() => {
|
||||||
|
if let Some(Ok(evt)) = event {
|
||||||
|
match evt {
|
||||||
|
Event::Key(key)
|
||||||
|
if key.kind == KeyEventKind::Press
|
||||||
|
=> self.handle_key_event(key),
|
||||||
|
Event::Mouse(_) => {}
|
||||||
|
Event::Resize(_, _) => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||||
|
match key_event.code {
|
||||||
|
KeyCode::Char('q') => self.exit(),
|
||||||
|
KeyCode::Char('s') => self.send(),
|
||||||
|
KeyCode::Char('r') => self.recv(),
|
||||||
|
KeyCode::Char('d') => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit(&mut self) {
|
||||||
|
self.screen = CurrentScreen::Stopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(&mut self) {
|
||||||
|
self.screen = CurrentScreen::Sending;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv(&mut self) {
|
||||||
|
self.screen = CurrentScreen::Receiving;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &App {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let title = Line::from(" Joecalsend ".bold());
|
||||||
|
let instructions = Line::from(vec![
|
||||||
|
" Send ".into(),
|
||||||
|
"<S>".blue().bold(),
|
||||||
|
" Receive ".into(),
|
||||||
|
"<R>".blue().bold(),
|
||||||
|
" Discover ".into(),
|
||||||
|
"<D>".blue().bold(),
|
||||||
|
" Quit ".into(),
|
||||||
|
"<Q> ".blue().bold(),
|
||||||
|
]);
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(title.centered())
|
||||||
|
.title_bottom(instructions.centered())
|
||||||
|
.border_set(border::THICK);
|
||||||
|
|
||||||
|
let current_screen = format!("{:?}", self.screen);
|
||||||
|
let text = Text::from(Line::from(current_screen.yellow()));
|
||||||
|
|
||||||
|
Paragraph::new(text)
|
||||||
|
.centered()
|
||||||
|
.block(block)
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use ratatui::{
|
||||||
widgets::{Block, Borders, List, ListItem},
|
widgets::{Block, Borders, List, ListItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::App;
|
use crate::{App, frontend::Peers};
|
||||||
|
|
||||||
// helper function to create a centered rect using up certain percentage of the
|
// helper function to create a centered rect using up certain percentage of the
|
||||||
// available rect `r`
|
// available rect `r`
|
||||||
|
@ -56,7 +56,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peers(peers: &crate::Peers, frame: &mut Frame, area: Rect) {
|
fn peers(peers: &Peers, frame: &mut Frame, area: Rect) {
|
||||||
let mut items = Vec::with_capacity(peers.len());
|
let mut items = Vec::with_capacity(peers.len());
|
||||||
for (k, v) in peers.iter() {
|
for (k, v) in peers.iter() {
|
||||||
let item = format!("{:?}: {} ({})", k, v.0, v.1);
|
let item = format!("{:?}: {} ({})", k, v.0, v.1);
|
158
src/main.rs
158
src/main.rs
|
@ -1,23 +1,12 @@
|
||||||
#![feature(slice_as_array)]
|
#![feature(slice_as_array)]
|
||||||
use std::{collections::BTreeMap, io, net::SocketAddr};
|
|
||||||
|
|
||||||
use futures::{FutureExt, StreamExt};
|
use frontend::App;
|
||||||
use joecalsend::{Config, JoecalState, error, models::Device};
|
use joecalsend::{Config, JoecalState, error, models::Device};
|
||||||
use local_ip_address::local_ip;
|
use local_ip_address::local_ip;
|
||||||
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
|
use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr};
|
||||||
use ratatui::{
|
|
||||||
DefaultTerminal,
|
|
||||||
buffer::Buffer,
|
|
||||||
crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind},
|
|
||||||
layout::Rect,
|
|
||||||
style::Stylize,
|
|
||||||
symbols::border,
|
|
||||||
text::{Line, Text},
|
|
||||||
widgets::{Block, Paragraph, Widget},
|
|
||||||
};
|
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
mod ui;
|
mod frontend;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> error::Result<()> {
|
async fn main() -> error::Result<()> {
|
||||||
|
@ -50,139 +39,28 @@ async fn main() -> error::Result<()> {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let mut handles = JoinSet::new();
|
let mut handles = JoinSet::new();
|
||||||
state.start(&config, &mut handles).await;
|
state.start(&config, &mut handles).await;
|
||||||
let mut app = App::new(state.clone()).await;
|
let mut app = App::new(state.clone());
|
||||||
let mut terminal = ratatui::init();
|
let mut terminal = ratatui::init();
|
||||||
let result = app.run(&mut terminal).await;
|
let result = app.run(&mut terminal).await;
|
||||||
ratatui::restore();
|
ratatui::restore();
|
||||||
|
|
||||||
while let Some(handle) = handles.join_next().await {
|
loop {
|
||||||
match handle {
|
tokio::select! {
|
||||||
Ok(h) => println!("Stopped {h:?}"),
|
handle = handles.join_next() => {
|
||||||
Err(e) => println!("Got error {e:?}"),
|
match handle {
|
||||||
|
Some(handle) => match handle {
|
||||||
|
Ok(h) => println!("Stopped {h:?}"),
|
||||||
|
Err(e) => println!("Got error {e:?}"),
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => {
|
||||||
|
handles.abort_all();
|
||||||
|
break;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result?)
|
Ok(result?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
state: JoecalState,
|
|
||||||
screen: CurrentScreen,
|
|
||||||
// addr -> (alias, fingerprint)
|
|
||||||
peers: Peers,
|
|
||||||
events: EventStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum CurrentScreen {
|
|
||||||
Main,
|
|
||||||
Sending,
|
|
||||||
Receiving,
|
|
||||||
Stopping,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub async fn new(state: JoecalState) -> Self {
|
|
||||||
App {
|
|
||||||
state,
|
|
||||||
screen: CurrentScreen::Main,
|
|
||||||
peers: Default::default(),
|
|
||||||
events: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> {
|
|
||||||
loop {
|
|
||||||
terminal.draw(|frame| self.draw(frame))?;
|
|
||||||
self.handle_events().await?;
|
|
||||||
|
|
||||||
if self.screen == CurrentScreen::Stopping {
|
|
||||||
self.state.stop().await;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let peers = self.state.peers.lock().await;
|
|
||||||
self.peers.clear();
|
|
||||||
peers.iter().for_each(|(k, v)| {
|
|
||||||
// k is fingerprint, v is addr, device
|
|
||||||
let addr = v.0;
|
|
||||||
let alias = v.1.alias.clone();
|
|
||||||
let fingerprint = k.clone();
|
|
||||||
self.peers.insert(addr, (alias, fingerprint));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_events(&mut self) -> io::Result<()> {
|
|
||||||
tokio::select! {
|
|
||||||
event = self.events.next().fuse() => {
|
|
||||||
if let Some(Ok(evt)) = event {
|
|
||||||
match evt {
|
|
||||||
Event::Key(key)
|
|
||||||
if key.kind == KeyEventKind::Press
|
|
||||||
=> self.handle_key_event(key),
|
|
||||||
Event::Mouse(_) => {}
|
|
||||||
Event::Resize(_, _) => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = tokio::time::sleep(tokio::time::Duration::from_millis(100)) => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
|
||||||
match key_event.code {
|
|
||||||
KeyCode::Char('q') => self.exit(),
|
|
||||||
KeyCode::Char('s') => self.send(),
|
|
||||||
KeyCode::Char('r') => self.recv(),
|
|
||||||
KeyCode::Char('d') => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit(&mut self) {
|
|
||||||
self.screen = CurrentScreen::Stopping;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send(&mut self) {
|
|
||||||
self.screen = CurrentScreen::Sending;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn recv(&mut self) {
|
|
||||||
self.screen = CurrentScreen::Receiving;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for &App {
|
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
||||||
let title = Line::from(" Joecalsend ".bold());
|
|
||||||
let instructions = Line::from(vec![
|
|
||||||
" Send ".into(),
|
|
||||||
"<S>".blue().bold(),
|
|
||||||
" Receive ".into(),
|
|
||||||
"<R>".blue().bold(),
|
|
||||||
" Discover ".into(),
|
|
||||||
"<D>".blue().bold(),
|
|
||||||
" Quit ".into(),
|
|
||||||
"<Q> ".blue().bold(),
|
|
||||||
]);
|
|
||||||
let block = Block::bordered()
|
|
||||||
.title(title.centered())
|
|
||||||
.title_bottom(instructions.centered())
|
|
||||||
.border_set(border::THICK);
|
|
||||||
|
|
||||||
let current_screen = format!("{:?}", self.screen);
|
|
||||||
let text = Text::from(Line::from(current_screen.yellow()));
|
|
||||||
|
|
||||||
Paragraph::new(text)
|
|
||||||
.centered()
|
|
||||||
.block(block)
|
|
||||||
.render(area, buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue