feat: entry search
This commit is contained in:
parent
8e6fa9be2f
commit
7c86e3d496
2 changed files with 130 additions and 82 deletions
|
|
@ -7,7 +7,7 @@ use iced::{
|
|||
};
|
||||
use rfd::{AsyncFileDialog, FileHandle};
|
||||
|
||||
use crate::db;
|
||||
use crate::db::{self, EntrySearchExt};
|
||||
|
||||
type Element<'a> = iced::Element<'a, Message, iced::Theme, iced::Renderer>;
|
||||
|
||||
|
|
@ -22,13 +22,15 @@ enum Message {
|
|||
OpenDb,
|
||||
DbOpened(Result<keepass::Database, String>),
|
||||
|
||||
SelectionChanged(usize, db::BasicField),
|
||||
ChangeSelection(usize, db::BasicField),
|
||||
|
||||
SelectionLeft,
|
||||
SelectionDown,
|
||||
SelectionUp,
|
||||
SelectionRight,
|
||||
|
||||
ChangeQuery(String),
|
||||
|
||||
Copy,
|
||||
|
||||
FocusNext,
|
||||
|
|
@ -42,9 +44,11 @@ struct State {
|
|||
|
||||
db: Option<keepass::Database>,
|
||||
|
||||
db_loaded: bool,
|
||||
db_view: Vec<keepass::db::Entry>,
|
||||
selected_entry: usize,
|
||||
selected_field: db::BasicField,
|
||||
query: String,
|
||||
|
||||
password: String,
|
||||
|
||||
|
|
@ -105,14 +109,15 @@ impl State {
|
|||
.iter_all_entries()
|
||||
.map(|entry_ref| entry_ref.clone())
|
||||
.collect();
|
||||
self.db = Some(db)
|
||||
self.db = Some(db);
|
||||
self.db_loaded = true;
|
||||
}
|
||||
Err(e) => self.error = e.into(),
|
||||
}
|
||||
self.status = "".into();
|
||||
}
|
||||
|
||||
Message::SelectionChanged(i, field) => {
|
||||
Message::ChangeSelection(i, field) => {
|
||||
self.selected_entry = i;
|
||||
self.selected_field = field;
|
||||
return Task::done(Message::Copy);
|
||||
|
|
@ -132,18 +137,37 @@ impl State {
|
|||
}
|
||||
Message::SelectionRight => self.selected_field.next(),
|
||||
|
||||
Message::ChangeQuery(str) => {
|
||||
self.query = str;
|
||||
self.db_view = self
|
||||
.db
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter_all_entries()
|
||||
.filter_map(|entry_ref| {
|
||||
entry_ref
|
||||
.contains(&self.query)
|
||||
.then(|| entry_ref.to_owned())
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
Message::Copy => {
|
||||
let entry_ref = &self.db_view[self.selected_entry];
|
||||
if let Some(entry_ref) = &self.db_view.get(self.selected_entry) {
|
||||
if let Some(str) = match self.selected_field {
|
||||
db::BasicField::Title => entry_ref.get_password(),
|
||||
db::BasicField::Username => entry_ref.get_username(),
|
||||
db::BasicField::Password => entry_ref.get_password(),
|
||||
db::BasicField::Url => entry_ref.get_url(),
|
||||
} {
|
||||
self.error = "".into();
|
||||
return clipboard::write(str.to_owned());
|
||||
} else {
|
||||
self.error = "nothing to copy!".into();
|
||||
}
|
||||
} else {
|
||||
self.error = "nothing selected!".into();
|
||||
}
|
||||
}
|
||||
|
||||
Message::FocusNext => return widget::operation::focus_next(),
|
||||
|
|
@ -153,7 +177,11 @@ impl State {
|
|||
Task::none()
|
||||
}
|
||||
fn view(&self) -> Element<'_> {
|
||||
let main = if !self.db_view.is_empty() {
|
||||
let main = if self.db_loaded {
|
||||
widget::column([
|
||||
widget::text_input("search query", &self.query)
|
||||
.on_input(Message::ChangeQuery)
|
||||
.into(),
|
||||
widget::scrollable(
|
||||
widget::grid(self.db_view.iter().enumerate().flat_map(|(i, entry)| {
|
||||
[
|
||||
|
|
@ -161,13 +189,14 @@ impl State {
|
|||
let field = db::BasicField::Title;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
if i == self.selected_entry && field == self.selected_field
|
||||
{
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.on_press(Message::ChangeSelection(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
|
|
@ -176,13 +205,14 @@ impl State {
|
|||
let field = db::BasicField::Username;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
if i == self.selected_entry && field == self.selected_field
|
||||
{
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.on_press(Message::ChangeSelection(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
|
|
@ -191,13 +221,14 @@ impl State {
|
|||
let field = db::BasicField::Password;
|
||||
widget::button(widget::text("[password hidden]"))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
if i == self.selected_entry && field == self.selected_field
|
||||
{
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.on_press(Message::ChangeSelection(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
|
|
@ -206,13 +237,14 @@ impl State {
|
|||
let field = db::BasicField::Url;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
if i == self.selected_entry && field == self.selected_field
|
||||
{
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.on_press(Message::ChangeSelection(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
|
|
@ -222,6 +254,11 @@ impl State {
|
|||
.height(Length::Shrink)
|
||||
.columns(4),
|
||||
)
|
||||
// I don't know why this needs to be here,
|
||||
// but without it the search bar isn't shown
|
||||
.height(Length::Fill)
|
||||
.into(),
|
||||
])
|
||||
.into()
|
||||
} else {
|
||||
widget::container(
|
||||
|
|
|
|||
11
src/db.rs
11
src/db.rs
|
|
@ -27,6 +27,17 @@ impl BasicField {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait EntrySearchExt {
|
||||
fn contains(&self, s: impl AsRef<str>) -> bool;
|
||||
}
|
||||
impl EntrySearchExt for keepass::db::EntryRef<'_> {
|
||||
fn contains(&self, s: impl AsRef<str>) -> bool {
|
||||
let str = s.as_ref();
|
||||
self.get_title().is_some_and(|title| title.contains(str))
|
||||
|| self.get_url().is_some_and(|url| url.contains(str))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_opened() -> io::Result<String> {
|
||||
Ok(fs::read_to_string(
|
||||
dirs::data_dir()
|
||||
|
|
|
|||
Loading…
Reference in a new issue