Compare commits

...

1 commit

Author SHA1 Message Date
e30e867933 First UX pass of new list command
All checks were successful
/ cargo test (push) Successful in 22s
2024-05-07 18:03:25 +02:00
3 changed files with 463 additions and 5 deletions

287
Cargo.lock generated
View file

@ -17,6 +17,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
@ -32,6 +44,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "anstream"
version = "0.6.11"
@ -226,6 +244,21 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.0.83"
@ -275,7 +308,7 @@ version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn",
@ -293,6 +326,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
@ -302,6 +348,31 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
dependencies = [
"winapi",
]
[[package]]
name = "either"
version = "1.10.0"
@ -330,8 +401,10 @@ version = "0.1.1"
dependencies = [
"axum",
"clap",
"crossterm",
"nix",
"qpidfile",
"ratatui",
"reqwest",
"serde",
"serde_json",
@ -436,6 +509,22 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@ -569,12 +658,27 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indoc"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.10"
@ -608,12 +712,31 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lru"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
dependencies = [
"hashbrown",
]
[[package]]
name = "matchit"
version = "0.7.3"
@ -648,6 +771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
@ -705,6 +829,35 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.0",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -767,6 +920,35 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ratatui"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a564a852040e82671dc50a37d88f3aa83bbc690dfc6844cfe7a2591620206a80"
dependencies = [
"bitflags",
"cassowary",
"compact_str",
"crossterm",
"indoc",
"itertools",
"lru",
"paste",
"stability",
"strum",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags",
]
[[package]]
name = "reqwest"
version = "0.12.2"
@ -896,6 +1078,12 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.197"
@ -958,6 +1146,27 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-mio"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"signal-hook",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@ -998,12 +1207,50 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stability"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff9eaf853dec4c8802325d8b6d3dffa86cc707fd7a1a4cdbf416e13b061787a"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "strum"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -1257,6 +1504,18 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -1292,6 +1551,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.1"
@ -1574,6 +1839,26 @@ version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "zerocopy"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zeroize"
version = "1.7.0"

View file

@ -8,7 +8,7 @@ edition = "2021"
[features]
default = ["server", "editor"]
server = ["tokio", "qpidfile", "axum"]
editor = ["reqwest", "tempfile", "which", "nix"]
editor = ["reqwest", "tempfile", "which", "nix", "ratatui", "crossterm"]
[dependencies]
qpidfile = { version = "0.9.2", optional = true }
@ -26,6 +26,8 @@ tempfile = { version = "3.10.1", optional = true }
which = { version = "6.0.1", optional = true }
nix = { version = "0.28.0", optional = true, default-features = false, features = ["signal"] }
urlencoding = { version = "2.1.3"}
ratatui = { version = "0.26.2", optional = true }
crossterm = { version = "0.27.0", optional = true }
[profile.release] # 💛 @Ryze@equestria.social
strip = "symbols"

View file

@ -1,9 +1,180 @@
use std::io;
use std::io::{Stdout, stdout};
use crate::schema::resource_list::ResourceList;
use std::path::PathBuf;
use crossterm::{event, ExecutableCommand, execute};
use crossterm::event::{Event, KeyCode, KeyEventKind};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph};
use thiserror::Error;
#[derive(Debug, Error)]
enum TerminalSetupError {
#[error("failed to enable raw mode: {0}")]
EnableRawMode(io::Error),
#[error("failed to enter alternate screen: {0}")]
EnterAlternateScreen(io::Error),
#[error("failed to create Ratatui terminal: {0}")]
TerminalCreation(io::Error),
}
#[derive(Debug, Error)]
enum TerminalRestore {
#[error("failed to disable raw mode for terminal: {0}")]
DisableRawMode(io::Error),
#[error("failed to leave alternate screen: {0}")]
LeaveAlternateScreen(io::Error),
}
fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>, TerminalSetupError> {
let mut stdout = std::io::stdout();
enable_raw_mode().map_err(TerminalSetupError::EnableRawMode)?;
execute!(stdout, EnterAlternateScreen).map_err(TerminalSetupError::EnterAlternateScreen)?;
Terminal::new(CrosstermBackend::new(stdout)).map_err(TerminalSetupError::TerminalCreation)
}
struct ResourceListGUI {
resources: ResourceList,
list_state: ListState,
}
impl ResourceListGUI {
fn new(resources: ResourceList) -> Self {
Self {
resources,
list_state: Default::default(),
}
}
fn draw(&mut self, terminal: &mut Terminal<impl Backend>) {
terminal.draw(|f| f.render_widget(self, f.size())).expect("Failed to draw self");
}
fn run(mut self, terminal: &mut Terminal<impl Backend>) {
loop {
self.draw(terminal);
if let Event::Key(key) = event::read().unwrap() {
if key.kind == KeyEventKind::Press {
use KeyCode::*;
match key.code {
Char('q') | Esc => return,
Char('j') | Down => self.next_item(),
Char('k') | Up => self.previous_item(),
Home => self.go_top(),
End => self.go_bottom(),
Char('d') => self.delete_current(),
_ => {}
}
}
}
}
}
fn next_item(&mut self) {
if self.resources.0.is_empty() {return}
let next_selected = if let Some(current_selected) = self.list_state.selected() {
if current_selected >= self.resources.0.len() - 1 {
0
} else {
current_selected + 1
}
} else {
0
};
self.list_state.select(Some(next_selected))
}
fn previous_item(&mut self) {
if self.resources.0.is_empty() {return}
let next_selected = if let Some(current_selected) = self.list_state.selected() {
if current_selected == 0 {
self.resources.0.len()-1
} else {
current_selected - 1
}
} else {
self.resources.0.len()-1
};
self.list_state.select(Some(next_selected))
}
fn go_top(&mut self) {
if self.resources.0.is_empty() {return}
self.list_state.select(Some(0))
}
fn go_bottom(&mut self) {
if self.resources.0.is_empty() {return}
self.list_state.select(Some(self.resources.0.len() - 1))
}
fn delete_current(&mut self) {
if let Some(selected) = self.list_state.selected() {
self.resources.0.remove(selected);
let next_selected = if self.resources.0.is_empty() {
None
} else {
Some(selected - 1)
};
self.list_state.select(next_selected);
}
}
}
impl Widget for &mut ResourceListGUI {
fn render(self, area: Rect, buf: &mut Buffer) where Self: Sized {
let [body, footer] = Layout::vertical([
Constraint::Min(1),
Constraint::Length(1),
]).areas(area);
let [list_area, details] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Fill(2)
]).areas(body);
let list_block = Block::new()
.borders(Borders::TOP)
.title("Resources");
let items = self.resources.0.iter().map(|resource| ListItem::new(resource.subject.clone()));
let list = List::new(items)
.block(list_block)
.highlight_symbol(">");
StatefulWidget::render(list, list_area, buf, &mut self.list_state);
if let Some(selected) = self.list_state.selected() {
let selected = &self.resources.0.get(selected).unwrap();
let details_block = Block::new()
.borders(Borders::LEFT | Borders::TOP)
.title("Resource details");
let text = Paragraph::new(serde_json::to_string_pretty(selected).unwrap_or_default())
.block(details_block);
text.render(details, buf);
}
let footer_text = Paragraph::new("Arrow keys navigate | d: delete")
.centered()
.on_dark_gray();
footer_text.render(footer, buf);
}
}
fn restore_terminal() -> Result<(), TerminalRestore>{
disable_raw_mode().map_err(TerminalRestore::DisableRawMode)?;
stdout().execute(LeaveAlternateScreen).map_err(TerminalRestore::LeaveAlternateScreen)?;
Ok(())
}
pub fn list(database_path: PathBuf) {
let resources = ResourceList::load(database_path).unwrap();
for resource in resources.0 {
println!("{}", resource.subject)
}
let mut terminal = setup_terminal().unwrap();
ResourceListGUI::new(resources).run(&mut terminal);
restore_terminal().unwrap();
}