Implement the password-store tree sidebar
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
pub mod action;
|
||||
pub mod app;
|
||||
pub mod runtime;
|
||||
pub mod sidebar;
|
||||
pub mod terminal;
|
||||
pub mod ui;
|
||||
|
||||
@@ -16,7 +17,7 @@ use ratatui::DefaultTerminal;
|
||||
|
||||
use crate::{
|
||||
action::resolve_key,
|
||||
app::{App, AsyncPayload, StartupSummary},
|
||||
app::{App, AppEffect, AsyncPayload, StartupData},
|
||||
runtime::AsyncExecutor,
|
||||
};
|
||||
|
||||
@@ -27,23 +28,16 @@ const TICK_INTERVAL: Duration = Duration::from_millis(250);
|
||||
pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
let mut app = App::new();
|
||||
let executor = AsyncExecutor::new();
|
||||
let startup = app.begin_request();
|
||||
executor.submit(startup, || {
|
||||
ironstorage::config::Config::load(None)
|
||||
.map(|config| {
|
||||
AsyncPayload::Startup(StartupSummary {
|
||||
configuration: config.source().display().to_string(),
|
||||
vault: config.vault().display().to_string(),
|
||||
})
|
||||
})
|
||||
.map_err(|error| error.to_string())
|
||||
});
|
||||
let startup = app.begin_latest_request();
|
||||
executor.submit(startup, || load_startup().map(AsyncPayload::Startup));
|
||||
|
||||
while !app.should_quit() {
|
||||
for result in executor.drain() {
|
||||
app.apply_result(result);
|
||||
}
|
||||
|
||||
let size = terminal.size()?;
|
||||
app.resize(size.width, size.height);
|
||||
terminal.draw(|frame| ui::draw(frame, &app))?;
|
||||
if !event::poll(TICK_INTERVAL)? {
|
||||
app.tick();
|
||||
@@ -52,8 +46,18 @@ pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == KeyEventKind::Press => {
|
||||
if let Some(action) = resolve_key(app.mode(), key.code, key.modifiers) {
|
||||
app.dispatch(action);
|
||||
if app.sidebar().is_editing_filter() {
|
||||
if handle_filter_key(&mut app, key.code) {
|
||||
submit_filter(&mut app, &executor);
|
||||
}
|
||||
} else if let Some(action) = resolve_key(app.mode(), key.code, key.modifiers)
|
||||
&& app.dispatch(action) == AppEffect::RefreshTree
|
||||
&& let Some(config) = app.config().cloned()
|
||||
{
|
||||
let token = app.begin_latest_request();
|
||||
executor.submit(token, move || {
|
||||
load_tree(&config).map(AsyncPayload::Refreshed)
|
||||
});
|
||||
}
|
||||
}
|
||||
Event::Resize(width, height) => app.resize(width, height),
|
||||
@@ -62,3 +66,58 @@ pub fn run(terminal: &mut DefaultTerminal) -> io::Result<()> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_startup() -> Result<StartupData, String> {
|
||||
let config = ironstorage::config::Config::load(None).map_err(|error| error.to_string())?;
|
||||
let tree = load_tree(&config)?;
|
||||
Ok(StartupData { config, tree })
|
||||
}
|
||||
|
||||
fn load_tree(config: &ironstorage::config::Config) -> Result<ironstorage::read::TreeModel, String> {
|
||||
let repository = ironstorage::repository::Repository::open(config.vault())
|
||||
.map_err(|error| error.to_string())?;
|
||||
let keys = ironstorage::crypto::KeyStore::load(config.key_material())
|
||||
.map_err(|error| error.to_string())?;
|
||||
ironstorage::read::VaultReader::new(&repository, &keys)
|
||||
.list(&ironstorage::repository::DirectoryPath::root())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn submit_filter(app: &mut App, executor: &AsyncExecutor) {
|
||||
let query = app.sidebar().filter_query().to_owned();
|
||||
if query.is_empty() {
|
||||
app.sidebar_mut().reset_filter_results();
|
||||
return;
|
||||
}
|
||||
let Some(config) = app.config().cloned() else {
|
||||
return;
|
||||
};
|
||||
let token = app.begin_latest_request();
|
||||
executor.submit(token, move || {
|
||||
let repository = ironstorage::repository::Repository::open(config.vault())
|
||||
.map_err(|error| error.to_string())?;
|
||||
let keys = ironstorage::crypto::KeyStore::load(config.key_material())
|
||||
.map_err(|error| error.to_string())?;
|
||||
let results = ironstorage::read::VaultReader::new(&repository, &keys)
|
||||
.find(std::slice::from_ref(&query))
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(AsyncPayload::Filtered { query, results })
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_filter_key(app: &mut App, code: crossterm::event::KeyCode) -> bool {
|
||||
use crossterm::event::KeyCode;
|
||||
match code {
|
||||
KeyCode::Esc => {
|
||||
app.sidebar_mut().clear_filter_results();
|
||||
false
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
app.sidebar_mut().finish_filter();
|
||||
false
|
||||
}
|
||||
KeyCode::Backspace => app.sidebar_mut().pop_filter_character(),
|
||||
KeyCode::Char(character) => app.sidebar_mut().push_filter_character(character),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user