remove native dialog dep
This commit is contained in:
parent
5907909470
commit
5f2e2f3eb2
6 changed files with 188 additions and 448 deletions
280
Cargo.lock
generated
280
Cargo.lock
generated
|
@ -97,12 +97,6 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
|
@ -237,15 +231,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
|
@ -449,15 +434,6 @@ dependencies = [
|
|||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
|
@ -470,18 +446,6 @@ dependencies = [
|
|||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -518,12 +482,6 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_home"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.8"
|
||||
|
@ -611,12 +569,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "formatx"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8866fac38f53fc87fa3ae1b09ddd723e0482f8fa74323518b4c59df2c55a00a"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
|
@ -1135,15 +1087,6 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
|
@ -1190,7 +1133,6 @@ dependencies = [
|
|||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-dialog",
|
||||
"network-interface",
|
||||
"ratatui",
|
||||
"reqwest",
|
||||
|
@ -1349,30 +1291,6 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-dialog"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f006431cea71a83e6668378cb5abc2d52af299cbac6dca1780c6eeca90822df"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"block2",
|
||||
"dirs",
|
||||
"dispatch2",
|
||||
"formatx",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation",
|
||||
"raw-window-handle",
|
||||
"thiserror 2.0.12",
|
||||
"versions",
|
||||
"wfd",
|
||||
"which",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
|
@ -1428,15 +1346,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
@ -1446,147 +1355,6 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
|
@ -1846,7 +1614,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
"itertools",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
|
@ -1855,12 +1623,6 @@ dependencies = [
|
|||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.13"
|
||||
|
@ -2627,7 +2389,7 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"itertools",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
@ -2685,16 +2447,6 @@ version = "0.9.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "versions"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80a7e511ce1795821207a837b7b1c8d8aca0c648810966ad200446ae58f6667f"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
@ -2800,28 +2552,6 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wfd"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "which"
|
||||
version = "7.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
|
||||
dependencies = [
|
||||
"either",
|
||||
"env_home",
|
||||
"rustix 1.0.7",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -3078,12 +2808,6 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winsafe"
|
||||
version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
|
|
@ -16,7 +16,6 @@ local-ip-address = "0.6"
|
|||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "2"
|
||||
native-dialog = "0.9"
|
||||
network-interface = { version = "2", features = ["serde"] }
|
||||
ratatui = "0.29"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
|
|
153
src/app/mod.rs
153
src/app/mod.rs
|
@ -1,36 +1,21 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
net::SocketAddr,
|
||||
sync::{LazyLock, OnceLock},
|
||||
time::Duration,
|
||||
};
|
||||
use std::{collections::BTreeMap, net::SocketAddr, sync::OnceLock, time::Duration};
|
||||
|
||||
use crossterm::event::{Event, EventStream, KeyCode, KeyEvent, KeyEventKind};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use joecalsend::{
|
||||
Config, JoecalState, Listeners, TransferEvent, UploadDialog,
|
||||
Config, JoecalState, JoecalUploadRequest, Listeners, TransferEvent, UploadDialog,
|
||||
error::{LocalSendError, Result},
|
||||
models::Device,
|
||||
};
|
||||
use julid::Julid;
|
||||
use log::{LevelFilter, error, info};
|
||||
use native_dialog::MessageDialogBuilder;
|
||||
use ratatui::{
|
||||
DefaultTerminal, Frame,
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{Style, Stylize},
|
||||
symbols::border,
|
||||
text::{Line, Text},
|
||||
widgets::{Block, Paragraph, Widget},
|
||||
};
|
||||
use ratatui::{DefaultTerminal, Frame};
|
||||
use tokio::{
|
||||
sync::mpsc::{UnboundedReceiver, unbounded_channel},
|
||||
task::JoinSet,
|
||||
};
|
||||
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
||||
|
||||
pub mod widgets;
|
||||
use widgets::*;
|
||||
|
||||
pub type Peers = BTreeMap<SocketAddr, (String, String)>;
|
||||
|
||||
|
@ -40,6 +25,7 @@ pub struct App {
|
|||
pub events: EventStream,
|
||||
// addr -> (alias, fingerprint)
|
||||
pub peers: Peers,
|
||||
pub uploads: BTreeMap<Julid, JoecalUploadRequest>,
|
||||
// for getting messages back from the web server or web client about things we've done; the
|
||||
// other end is held by the state
|
||||
transfer_event_rx: OnceLock<UnboundedReceiver<TransferEvent>>,
|
||||
|
@ -67,6 +53,7 @@ impl App {
|
|||
screen: vec![CurrentScreen::Main],
|
||||
peers: Default::default(),
|
||||
events: Default::default(),
|
||||
uploads: Default::default(),
|
||||
transfer_event_rx: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -131,31 +118,13 @@ impl App {
|
|||
transfer_event = self.transfer_event_rx.get_mut().unwrap().recv() => {
|
||||
if let Some(event) = transfer_event {
|
||||
match event {
|
||||
TransferEvent::UploadRequest { alias, id } => {
|
||||
let sender =
|
||||
self
|
||||
.state
|
||||
.get()
|
||||
.unwrap()
|
||||
.get_upload_request(id)
|
||||
.await
|
||||
.ok_or(LocalSendError::SessionInactive)?;
|
||||
|
||||
// TODO: replace this with ratatui widget dialog
|
||||
let upload_confirmed = MessageDialogBuilder::default()
|
||||
.set_title(&alias)
|
||||
.set_text("Do you want to receive files from this device?")
|
||||
.confirm()
|
||||
.show()
|
||||
.unwrap();
|
||||
|
||||
if upload_confirmed {
|
||||
let _ = sender.send(UploadDialog::UploadConfirm);
|
||||
} else {
|
||||
let _ = sender.send(UploadDialog::UploadDeny);
|
||||
}
|
||||
TransferEvent::UploadRequest { id, request } => {
|
||||
self.uploads.insert(id, request);
|
||||
}
|
||||
TransferEvent::Sent => {}
|
||||
TransferEvent::Received(id) => {
|
||||
self.uploads.remove(&id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -236,106 +205,6 @@ fn change_log_level(delta: isize) {
|
|||
log::set_max_level(level);
|
||||
}
|
||||
|
||||
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(),
|
||||
])
|
||||
});
|
||||
|
||||
impl Widget for &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 header_layout =
|
||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]);
|
||||
let [_header_left, header_right] = header_layout.areas(top);
|
||||
|
||||
let mode = self.screen.last().unwrap();
|
||||
match mode {
|
||||
CurrentScreen::Main => {
|
||||
main_page(*mode, &MAIN_MENU, area, buf);
|
||||
logger(header_right.inner(Margin::new(1, 2)), buf);
|
||||
let peers = PeersWidget { peers: &self.peers };
|
||||
peers.render(footer_right.inner(Margin::new(1, 1)), buf);
|
||||
NetworkInfoWidget.render(footer_left.inner(Margin::new(1, 1)), buf);
|
||||
}
|
||||
CurrentScreen::Logging => {
|
||||
main_page(*mode, &LOGGING_MENU, area, buf);
|
||||
logger(area.inner(Margin::new(2, 4)), buf);
|
||||
}
|
||||
CurrentScreen::Receiving => {
|
||||
main_page(*mode, &MAIN_MENU, area, 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()
|
||||
.border_set(border::THICK)
|
||||
.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);
|
||||
}
|
||||
|
||||
async fn shutdown(handles: &mut JoinSet<Listeners>) {
|
||||
let mut alarm = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||
alarm.tick().await;
|
||||
|
|
|
@ -1,12 +1,147 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use joecalsend::JoecalUploadRequest;
|
||||
use log::LevelFilter;
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Rect},
|
||||
style::Stylize,
|
||||
text::{Line, ToLine},
|
||||
widgets::{Block, Borders, List, ListItem, Padding, Row, Table, Widget},
|
||||
layout::{Constraint, Layout, Margin, Rect},
|
||||
style::{Style, Stylize},
|
||||
symbols::border,
|
||||
text::{Line, Text, ToLine},
|
||||
widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Row, Table, Widget},
|
||||
};
|
||||
use tui_logger::{TuiLoggerLevelOutput, TuiLoggerWidget, TuiWidgetState};
|
||||
|
||||
use super::Peers;
|
||||
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(),
|
||||
])
|
||||
});
|
||||
|
||||
impl Widget for &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();
|
||||
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);
|
||||
let ups: Vec<_> = self.uploads.values().collect();
|
||||
uploads(&ups, 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, &MAIN_MENU, area, 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()
|
||||
.border_set(border::THICK)
|
||||
.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], area: Rect, buf: &mut Buffer) {
|
||||
//let mut lines = Vec::new();
|
||||
let title = Line::from(" Upload Requests ").bold();
|
||||
let block = Block::bordered()
|
||||
.title(title.centered())
|
||||
.border_set(border::THICK);
|
||||
let mut rows = Vec::new();
|
||||
for &req in requests {
|
||||
let src = req.alias.to_line().left_aligned();
|
||||
let files = req
|
||||
.files
|
||||
.values()
|
||||
.map(|f| f.file_name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
let files = files.join(", ");
|
||||
let files = Line::from(files);
|
||||
rows.push(Row::new([src, files.centered()]));
|
||||
}
|
||||
let widths = [Constraint::Max(20), Constraint::Min(50)];
|
||||
let table = Table::new(rows, widths)
|
||||
.block(block)
|
||||
.header(Row::new(["Sender".bold(), "Files".bold()]));
|
||||
table.render(area, buf);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeersWidget<'p> {
|
||||
|
@ -44,13 +179,10 @@ impl Widget for NetworkInfoWidget {
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// let rows = Rows::new(rect);
|
||||
// let mut table = Table::default();
|
||||
|
||||
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.into(), uaddr.to_line().right_aligned()]);
|
||||
let udp = Row::new(vec![udp, uaddr.to_line().right_aligned()]);
|
||||
|
||||
let mip = format!(
|
||||
"{:?}:{:?}",
|
||||
|
|
28
src/lib.rs
28
src/lib.rs
|
@ -12,7 +12,7 @@ use std::{
|
|||
|
||||
use julid::Julid;
|
||||
use log::error;
|
||||
use models::Device;
|
||||
use models::{Device, FileMetadata};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
net::UdpSocket,
|
||||
|
@ -46,9 +46,19 @@ pub enum UploadDialog {
|
|||
|
||||
pub enum TransferEvent {
|
||||
Sent,
|
||||
Received,
|
||||
Received(Julid),
|
||||
Failed,
|
||||
UploadRequest { alias: String, id: Julid },
|
||||
UploadRequest {
|
||||
id: Julid,
|
||||
request: JoecalUploadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JoecalUploadRequest {
|
||||
pub alias: String,
|
||||
pub files: HashMap<String, FileMetadata>,
|
||||
pub tx: UnboundedSender<UploadDialog>,
|
||||
}
|
||||
|
||||
/// Contains the main network and backend state for an application session.
|
||||
|
@ -60,7 +70,7 @@ pub struct JoecalState {
|
|||
pub running_state: Arc<Mutex<RunningState>>,
|
||||
pub socket: Arc<UdpSocket>,
|
||||
pub client: reqwest::Client,
|
||||
upload_requests: Arc<Mutex<HashMap<Julid, UnboundedSender<UploadDialog>>>>,
|
||||
pub upload_requests: Arc<Mutex<HashMap<Julid, JoecalUploadRequest>>>,
|
||||
shutdown_sender: OnceLock<ShutdownSender>,
|
||||
// the receiving end will be held by the application so it can update the UI based on backend
|
||||
// events
|
||||
|
@ -152,7 +162,7 @@ impl JoecalState {
|
|||
peers.clear();
|
||||
}
|
||||
|
||||
pub async fn get_upload_request(&self, id: Julid) -> Option<UnboundedSender<UploadDialog>> {
|
||||
pub async fn get_upload_request(&self, id: Julid) -> Option<JoecalUploadRequest> {
|
||||
self.upload_requests.lock().await.get(&id).cloned()
|
||||
}
|
||||
|
||||
|
@ -166,8 +176,12 @@ impl JoecalState {
|
|||
///
|
||||
/// IMPORTANT! Be sure to call `clear_upload_request(id)` when you're done
|
||||
/// getting an answer back/before you exit!
|
||||
pub async fn add_upload_request(&self, id: Julid, tx: UnboundedSender<UploadDialog>) {
|
||||
self.upload_requests.lock().await.entry(id).insert_entry(tx);
|
||||
pub async fn add_upload_request(&self, id: Julid, request: JoecalUploadRequest) {
|
||||
self.upload_requests
|
||||
.lock()
|
||||
.await
|
||||
.entry(id)
|
||||
.insert_entry(request);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ use axum::{
|
|||
response::IntoResponse,
|
||||
};
|
||||
use julid::Julid;
|
||||
use log::{debug, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
|
||||
use crate::{
|
||||
JoecalState, TransferEvent, UploadDialog,
|
||||
JoecalState, JoecalUploadRequest, TransferEvent, UploadDialog,
|
||||
error::{LocalSendError, Result},
|
||||
models::{Device, FileMetadata},
|
||||
};
|
||||
|
@ -199,22 +199,24 @@ pub async fn register_prepare_upload(
|
|||
let id = Julid::new();
|
||||
let (tx, mut rx) = unbounded_channel();
|
||||
// be sure to clear this request before this function exits!
|
||||
state.add_upload_request(id, tx).await;
|
||||
|
||||
let dialog_send = state.transfer_event_tx.send(TransferEvent::UploadRequest {
|
||||
let request = JoecalUploadRequest {
|
||||
alias: req.info.alias.clone(),
|
||||
id,
|
||||
});
|
||||
match dialog_send {
|
||||
files: req.files.clone(),
|
||||
tx,
|
||||
};
|
||||
|
||||
match state
|
||||
.transfer_event_tx
|
||||
.send(TransferEvent::UploadRequest { id, request })
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(_e) => {
|
||||
state.clear_upload_request(id).await;
|
||||
Err(e) => {
|
||||
error!("error sending transfer event to app: {e:?}");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||
}
|
||||
}
|
||||
|
||||
let confirmation = rx.recv().await;
|
||||
state.clear_upload_request(id).await;
|
||||
|
||||
let Some(confirmation) = confirmation else {
|
||||
// the frontend must have dropped the tx before trying to send a reply back
|
||||
|
|
Loading…
Reference in a new issue