269 lines
8.3 KiB
Rust
269 lines
8.3 KiB
Rust
use std::sync::LazyLock;
|
|
|
|
use joecalsend::JoecalUploadRequest;
|
|
use log::LevelFilter;
|
|
use ratatui::{
|
|
buffer::Buffer,
|
|
layout::{Constraint, Layout, Margin, Rect},
|
|
style::{Color, Style, Stylize},
|
|
symbols::border,
|
|
text::{Line, Text, ToLine},
|
|
widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Row, Table, TableState, Widget},
|
|
};
|
|
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
|
|
|
use super::{App, CurrentScreen, Peers};
|
|
|
|
static MAIN_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
Line::from(vec![
|
|
" Send ".into(),
|
|
"<S>".blue().bold(),
|
|
" Receive ".into(),
|
|
"<R>".blue().bold(),
|
|
" Logs ".into(),
|
|
"<L>".blue().bold(),
|
|
" Previous Screen ".into(),
|
|
"<ESC>".blue().bold(),
|
|
" Quit ".into(),
|
|
"<Q>".blue().bold(),
|
|
])
|
|
});
|
|
|
|
static LOGGING_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
Line::from(vec![
|
|
" Reduce Logging Level ".into(),
|
|
"<LEFT>".blue().bold(),
|
|
" Increase Logging Level ".into(),
|
|
"<RIGHT>".blue().bold(),
|
|
" Previous Screen ".into(),
|
|
"<ESC>".blue().bold(),
|
|
" Quit ".into(),
|
|
"<Q>".blue().bold(),
|
|
])
|
|
});
|
|
|
|
static UPLOADS_MENU: LazyLock<Line> = LazyLock::new(|| {
|
|
Line::from(vec![
|
|
" Select Previous ".into(),
|
|
"<UP>".blue().bold(),
|
|
" Select Next ".into(),
|
|
"<DOWN>".blue().bold(),
|
|
" Approve Selection ".into(),
|
|
"<A>".blue().bold(),
|
|
" Deny Selection ".into(),
|
|
"<D>".blue().bold(),
|
|
" Previous Screen ".into(),
|
|
"<ESC>".blue().bold(),
|
|
" Quit ".into(),
|
|
"<Q>".blue().bold(),
|
|
])
|
|
});
|
|
|
|
impl Widget for &mut App {
|
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
|
let main_layout =
|
|
Layout::vertical([Constraint::Min(5), Constraint::Min(10), Constraint::Min(3)]);
|
|
let [top, _middle, bottom] = main_layout.areas(area);
|
|
|
|
let footer_layout =
|
|
Layout::horizontal([Constraint::Percentage(30), Constraint::Percentage(70)]);
|
|
let [footer_left, footer_right] = footer_layout.areas(bottom);
|
|
let footer_margin = Margin::new(1, 1);
|
|
|
|
let header_layout =
|
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
|
let [header_left, header_right] = header_layout.areas(top);
|
|
let header_margin = Margin::new(1, 2);
|
|
|
|
let mode = self.screen.last().unwrap();
|
|
let ups: Vec<_> = self.uploads.values().collect();
|
|
match mode {
|
|
CurrentScreen::Main => {
|
|
main_page(*mode, &MAIN_MENU, area, buf);
|
|
logger(header_right.inner(header_margin), buf);
|
|
let peers = PeersWidget { peers: &self.peers };
|
|
peers.render(footer_right.inner(footer_margin), buf);
|
|
NetworkInfoWidget.render(footer_left.inner(footer_margin), buf);
|
|
uploads(
|
|
&ups,
|
|
&mut self.upload_state,
|
|
header_left.inner(header_margin),
|
|
buf,
|
|
);
|
|
}
|
|
CurrentScreen::Logging => {
|
|
main_page(*mode, &LOGGING_MENU, area, buf);
|
|
logger(area.inner(Margin::new(2, 4)), buf);
|
|
}
|
|
CurrentScreen::Receiving => {
|
|
main_page(*mode, &UPLOADS_MENU, area, buf);
|
|
uploads(
|
|
&ups,
|
|
&mut self.upload_state,
|
|
area.inner(Margin::new(2, 4)),
|
|
buf,
|
|
);
|
|
}
|
|
_ => {
|
|
main_page(*mode, &MAIN_MENU, area, buf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn logger(area: Rect, buf: &mut Buffer) {
|
|
let title = Line::from(log::max_level().as_str());
|
|
let logger = TuiLoggerWidget::default()
|
|
.output_separator('|')
|
|
.output_timestamp(Some("%H:%M:%S%.3f".to_string()))
|
|
.output_level(Some(TuiLoggerLevelOutput::Abbreviated))
|
|
.output_target(true)
|
|
.output_file(false)
|
|
.output_line(false)
|
|
.block(Block::bordered().title(title.centered()))
|
|
.style(Style::default())
|
|
.state(&TuiWidgetState::new().set_default_display_level(LevelFilter::Debug));
|
|
logger.render(area, buf);
|
|
}
|
|
|
|
fn main_page(screen: CurrentScreen, menu: &Line, area: Rect, buf: &mut Buffer) {
|
|
let title = Line::from(" Joecalsend ".bold());
|
|
let block = Block::bordered()
|
|
.title(title.centered())
|
|
.title_bottom(menu.clone().centered())
|
|
.border_set(border::THICK);
|
|
|
|
let current_screen = format!("{screen:?}",);
|
|
let text = Text::from(Line::from(current_screen.yellow()));
|
|
|
|
Paragraph::new(text)
|
|
.centered()
|
|
.block(block)
|
|
.render(area, buf);
|
|
}
|
|
|
|
fn uploads(
|
|
requests: &[&JoecalUploadRequest],
|
|
state: &mut TableState,
|
|
area: Rect,
|
|
buf: &mut Buffer,
|
|
) {
|
|
let title = Line::from(" Upload Requests ").bold();
|
|
let block = Block::bordered().title(title.centered());
|
|
|
|
let mut rows = Vec::new();
|
|
for &req in requests {
|
|
let src = req.alias.to_line().left_aligned();
|
|
let mut size = 0;
|
|
let files = req
|
|
.files
|
|
.values()
|
|
.map(|f| {
|
|
size += f.size;
|
|
f.file_name.clone()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let files = files.join(", ");
|
|
let files = Line::from(files).centered();
|
|
let size = Line::from(format!("{size}")).centered();
|
|
rows.push(Row::new([src, size, files]));
|
|
}
|
|
|
|
if state.selected().is_none() && !rows.is_empty() {
|
|
state.select(Some(0));
|
|
} else if rows.is_empty() {
|
|
state.select(None);
|
|
};
|
|
|
|
let widths = [
|
|
Constraint::Max(20),
|
|
Constraint::Max(15),
|
|
Constraint::Min(50),
|
|
];
|
|
|
|
let table = Table::new(rows, widths)
|
|
.block(block)
|
|
.header(Row::new([
|
|
"Sender".bold().into_left_aligned_line(),
|
|
"Bytes".bold().into_centered_line(),
|
|
"Files".bold().into_centered_line(),
|
|
]))
|
|
.row_highlight_style(Style::new().bg(Color::Rgb(99, 99, 99)));
|
|
|
|
ratatui::widgets::StatefulWidget::render(table, area, buf, state);
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct PeersWidget<'p> {
|
|
pub peers: &'p Peers,
|
|
}
|
|
|
|
impl<'p> Widget for PeersWidget<'p> {
|
|
fn render(self, area: Rect, buf: &mut Buffer)
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let mut items = Vec::with_capacity(self.peers.len());
|
|
for (k, v) in self.peers.iter() {
|
|
let item = format!("{:?}: {} ({})", k, v.0, v.1);
|
|
let s = Line::from(item.yellow());
|
|
|
|
let item = ListItem::new(s);
|
|
items.push(item);
|
|
}
|
|
let title = Line::from(" Peers ".bold()).centered();
|
|
let block = Block::default()
|
|
.title(title)
|
|
.borders(Borders::all())
|
|
.padding(Padding::uniform(1));
|
|
let list = List::new(items).block(block);
|
|
list.render(area, buf);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct NetworkInfoWidget;
|
|
|
|
impl Widget for NetworkInfoWidget {
|
|
fn render(self, area: Rect, buf: &mut Buffer)
|
|
where
|
|
Self: Sized,
|
|
{
|
|
let udp = "UDP socket".yellow();
|
|
let udp = udp.to_line().left_aligned();
|
|
let uaddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR).yellow();
|
|
let udp = Row::new(vec![udp, uaddr.to_line().right_aligned()]);
|
|
|
|
let mip = format!(
|
|
"{:?}:{:?}",
|
|
joecalsend::MULTICAST_IP,
|
|
joecalsend::DEFAULT_PORT
|
|
)
|
|
.yellow();
|
|
let multicast = "Multicast address".yellow();
|
|
let multicast = Row::new(vec![
|
|
multicast.to_line().left_aligned(),
|
|
mip.to_line().right_aligned(),
|
|
]);
|
|
|
|
let haddr = format!("{:?}", joecalsend::LISTENING_SOCKET_ADDR).yellow();
|
|
let http = "HTTP address".yellow();
|
|
let http = Row::new(vec![
|
|
http.to_line().left_aligned(),
|
|
haddr.to_line().right_aligned(),
|
|
]);
|
|
|
|
let rows = vec![udp, multicast, http];
|
|
let widths = vec![Constraint::Percentage(50), Constraint::Percentage(50)];
|
|
|
|
let title = Line::from(" Listeners ".bold()).centered();
|
|
|
|
let block = Block::default()
|
|
.title(title)
|
|
.borders(Borders::all())
|
|
.padding(Padding::uniform(1));
|
|
|
|
let table = Table::new(rows, widths).block(block);
|
|
table.render(area, buf);
|
|
}
|
|
}
|