feat: copying entry fields
This commit is contained in:
parent
95bfede1e0
commit
b7c5a05c6e
4 changed files with 420 additions and 476 deletions
715
Cargo.lock
generated
715
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,4 +10,7 @@ keepass = "0.13.22"
|
|||
rfd = "0.17.2"
|
||||
|
||||
[dependencies.iced]
|
||||
version = "0.14.0"
|
||||
version = "0.15.0-dev"
|
||||
|
||||
[patch.crates-io]
|
||||
iced.git = "https://github.com/iced-rs/iced"
|
||||
|
|
|
|||
151
src/app/mod.rs
151
src/app/mod.rs
|
|
@ -1,7 +1,7 @@
|
|||
use std::{borrow::Cow, fs};
|
||||
|
||||
use iced::{
|
||||
Alignment, Color, Length, Subscription, Task, application, event,
|
||||
Alignment, Color, Length, Subscription, Task, application, clipboard, event,
|
||||
keyboard::{self, Key, key},
|
||||
theme, widget,
|
||||
};
|
||||
|
|
@ -22,9 +22,15 @@ enum Message {
|
|||
OpenDb,
|
||||
DbOpened(Result<keepass::Database, String>),
|
||||
|
||||
SelectionChanged(usize),
|
||||
SelectionUp,
|
||||
SelectionChanged(usize, db::BasicField),
|
||||
|
||||
SelectionLeft,
|
||||
SelectionDown,
|
||||
SelectionUp,
|
||||
SelectionRight,
|
||||
|
||||
Copy,
|
||||
Copied(Result<(), clipboard::Error>),
|
||||
|
||||
FocusNext,
|
||||
FocusPrevious,
|
||||
|
|
@ -39,6 +45,7 @@ struct State {
|
|||
|
||||
db_view: Vec<keepass::db::Entry>,
|
||||
selected_entry: usize,
|
||||
selected_field: db::BasicField,
|
||||
|
||||
password: String,
|
||||
|
||||
|
|
@ -106,19 +113,45 @@ impl State {
|
|||
self.status = "".into();
|
||||
}
|
||||
|
||||
Message::SelectionChanged(val) => self.selected_entry = val,
|
||||
Message::SelectionUp => {
|
||||
self.selected_entry =
|
||||
dbg!((self.selected_entry.wrapping_sub(1)).clamp(0, self.db_view.len() - 1))
|
||||
Message::SelectionChanged(i, field) => {
|
||||
self.selected_entry = i;
|
||||
self.selected_field = field;
|
||||
return Task::done(Message::Copy);
|
||||
}
|
||||
Message::SelectionLeft => self.selected_field.previous(),
|
||||
Message::SelectionDown => {
|
||||
self.selected_entry = if self.selected_entry == self.db_view.len() - 1 {
|
||||
// essentially do a wrapping_add up to the highest index in the view
|
||||
0
|
||||
} else {
|
||||
dbg!((self.selected_entry.wrapping_add(1)).clamp(0, self.db_view.len() - 1))
|
||||
(self.selected_entry.saturating_add(1)).clamp(0, self.db_view.len() - 1)
|
||||
}
|
||||
}
|
||||
Message::SelectionUp => {
|
||||
self.selected_entry =
|
||||
(self.selected_entry.saturating_sub(1)).clamp(0, self.db_view.len() - 1)
|
||||
}
|
||||
Message::SelectionRight => self.selected_field.next(),
|
||||
|
||||
Message::Copy => {
|
||||
let entry_ref = &self.db_view[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(),
|
||||
} {
|
||||
return clipboard::write(str.to_owned()).map(Message::Copied);
|
||||
} else {
|
||||
self.error = "nothing to copy!".into();
|
||||
}
|
||||
}
|
||||
Message::Copied(result) => {
|
||||
self.error = result
|
||||
.err()
|
||||
.map(|e| format!("{e:?}").into())
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
Message::FocusNext => return widget::operation::focus_next(),
|
||||
Message::FocusPrevious => return widget::operation::focus_previous(),
|
||||
|
|
@ -131,34 +164,70 @@ impl State {
|
|||
widget::scrollable(
|
||||
widget::grid(self.db_view.iter().enumerate().flat_map(|(i, entry)| {
|
||||
[
|
||||
widget::button(widget::text(entry.get_title().unwrap_or_default()))
|
||||
.style(if i == self.selected_entry {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
})
|
||||
.on_press(Message::SelectionChanged(i))
|
||||
.into(),
|
||||
widget::button(widget::text(entry.get_username().unwrap_or_default()))
|
||||
.style(if i == self.selected_entry {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
})
|
||||
.on_press(Message::SelectionChanged(i))
|
||||
.into(),
|
||||
widget::button(widget::text(entry.get_url().unwrap_or_default()))
|
||||
.style(if i == self.selected_entry {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
})
|
||||
.on_press(Message::SelectionChanged(i))
|
||||
.into(),
|
||||
if let Some(title) = entry.get_title() {
|
||||
let field = db::BasicField::Title;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
},
|
||||
if let Some(title) = entry.get_username() {
|
||||
let field = db::BasicField::Username;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
},
|
||||
if entry.get_password().is_some() {
|
||||
let field = db::BasicField::Password;
|
||||
widget::button(widget::text("[password hidden]"))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
},
|
||||
if let Some(title) = entry.get_url() {
|
||||
let field = db::BasicField::Url;
|
||||
widget::button(widget::text(title))
|
||||
.style(
|
||||
if i == self.selected_entry && field == self.selected_field {
|
||||
widget::button::primary
|
||||
} else {
|
||||
widget::button::subtle
|
||||
},
|
||||
)
|
||||
.on_press(Message::SelectionChanged(i, field))
|
||||
.into()
|
||||
} else {
|
||||
widget::space().into()
|
||||
},
|
||||
]
|
||||
}))
|
||||
.height(Length::Shrink)
|
||||
.columns(3),
|
||||
.columns(4),
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
|
|
@ -238,12 +307,20 @@ impl State {
|
|||
}
|
||||
Key::Character("p" | "f" | "i") if !db_loaded => Some(Message::PickDb),
|
||||
|
||||
Key::Character("k") | Key::Named(key::Named::ArrowUp) => {
|
||||
Some(Message::SelectionUp)
|
||||
Key::Character("h") | Key::Named(key::Named::ArrowLeft) => {
|
||||
Some(Message::SelectionLeft)
|
||||
}
|
||||
Key::Character("j") | Key::Named(key::Named::ArrowDown) => {
|
||||
Some(Message::SelectionDown)
|
||||
}
|
||||
Key::Character("k") | Key::Named(key::Named::ArrowUp) => {
|
||||
Some(Message::SelectionUp)
|
||||
}
|
||||
Key::Character("l") | Key::Named(key::Named::ArrowRight) => {
|
||||
Some(Message::SelectionRight)
|
||||
}
|
||||
|
||||
Key::Character("y" | "c") if db_loaded => Some(Message::Copy),
|
||||
|
||||
// ignore unused keys
|
||||
_ => None,
|
||||
|
|
@ -283,10 +360,10 @@ pub fn run() -> Result<(), iced::Error> {
|
|||
.subscription(State::subscription)
|
||||
.theme(theme::Theme::custom(
|
||||
"high-contrast-dark",
|
||||
theme::Palette {
|
||||
theme::palette::Seed {
|
||||
background: Color::from_rgb8(10, 10, 10),
|
||||
text: Color::WHITE,
|
||||
..theme::Palette::DARK
|
||||
..theme::palette::Seed::DARK
|
||||
},
|
||||
))
|
||||
.run()
|
||||
|
|
|
|||
27
src/db.rs
27
src/db.rs
|
|
@ -1,5 +1,32 @@
|
|||
use std::{fs, io};
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum BasicField {
|
||||
Title,
|
||||
#[default]
|
||||
Username,
|
||||
Password,
|
||||
Url,
|
||||
}
|
||||
impl BasicField {
|
||||
pub fn next(&mut self) {
|
||||
*self = match self {
|
||||
Self::Title => Self::Username,
|
||||
Self::Username => Self::Password,
|
||||
Self::Password => Self::Url,
|
||||
Self::Url => Self::Title,
|
||||
}
|
||||
}
|
||||
pub fn previous(&mut self) {
|
||||
*self = match self {
|
||||
Self::Title => Self::Url,
|
||||
Self::Username => Self::Title,
|
||||
Self::Password => Self::Username,
|
||||
Self::Url => Self::Password,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_last_opened() -> io::Result<String> {
|
||||
Ok(fs::read_to_string(
|
||||
dirs::data_dir()
|
||||
|
|
|
|||
Loading…
Reference in a new issue