Add desktop store and entry creation workflows
This commit is contained in:
@@ -6,6 +6,8 @@ use iced::keyboard::{self, key::Named};
|
||||
pub enum UiAction {
|
||||
About,
|
||||
Settings,
|
||||
InitializeStore,
|
||||
NewFolder,
|
||||
NewEntry,
|
||||
OpenFolder,
|
||||
OpenEntry,
|
||||
@@ -24,6 +26,7 @@ pub enum UiAction {
|
||||
Refresh,
|
||||
ReloadEntry,
|
||||
EditEntry,
|
||||
GeneratePassword,
|
||||
ToggleReveal,
|
||||
Lock,
|
||||
Minimize,
|
||||
@@ -35,6 +38,8 @@ impl UiAction {
|
||||
match self {
|
||||
Self::About => "about",
|
||||
Self::Settings => "settings",
|
||||
Self::InitializeStore => "initialize-store",
|
||||
Self::NewFolder => "new-folder",
|
||||
Self::NewEntry => "new-entry",
|
||||
Self::OpenFolder => "open-folder",
|
||||
Self::OpenEntry => "open-entry",
|
||||
@@ -53,6 +58,7 @@ impl UiAction {
|
||||
Self::Refresh => "refresh",
|
||||
Self::ReloadEntry => "reload-entry",
|
||||
Self::EditEntry => "edit-entry",
|
||||
Self::GeneratePassword => "generate-password",
|
||||
Self::ToggleReveal => "toggle-reveal",
|
||||
Self::Lock => "lock",
|
||||
Self::Minimize => "minimize",
|
||||
@@ -112,6 +118,7 @@ pub struct ActionContext {
|
||||
pub modal_open: bool,
|
||||
pub focused_field: bool,
|
||||
pub focused_sensitive: bool,
|
||||
pub focused_generatable: bool,
|
||||
pub entry_path: bool,
|
||||
}
|
||||
|
||||
@@ -126,6 +133,13 @@ pub struct ActionSpec {
|
||||
pub const ACTIONS: &[ActionSpec] = &[
|
||||
spec(UiAction::About, MenuGroup::App, "About IronStorage", None),
|
||||
spec(UiAction::Settings, MenuGroup::App, "Settings…", Some("⌘,")),
|
||||
spec(
|
||||
UiAction::InitializeStore,
|
||||
MenuGroup::File,
|
||||
"Initialize Store…",
|
||||
None,
|
||||
),
|
||||
spec(UiAction::NewFolder, MenuGroup::File, "New Folder…", None),
|
||||
spec(
|
||||
UiAction::Quit,
|
||||
MenuGroup::App,
|
||||
@@ -179,6 +193,12 @@ pub const ACTIONS: &[ActionSpec] = &[
|
||||
None,
|
||||
),
|
||||
spec(UiAction::EditEntry, MenuGroup::Entry, "Edit Entry", None),
|
||||
spec(
|
||||
UiAction::GeneratePassword,
|
||||
MenuGroup::Entry,
|
||||
"Generate Password…",
|
||||
None,
|
||||
),
|
||||
spec(
|
||||
UiAction::ToggleReveal,
|
||||
MenuGroup::Entry,
|
||||
@@ -244,8 +264,13 @@ pub fn enabled(action: UiAction, context: ActionContext) -> bool {
|
||||
&& !context.modal_open
|
||||
}
|
||||
UiAction::CommandPalette => !context.modal_open,
|
||||
// Entry creation is not valid until the dedicated workflow exists.
|
||||
UiAction::NewEntry => false,
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry => {
|
||||
context.storage_ready
|
||||
&& !context.dirty
|
||||
&& !context.saving
|
||||
&& !context.switching_vault
|
||||
&& !context.modal_open
|
||||
}
|
||||
UiAction::OpenFolder => {
|
||||
context.storage_ready && !context.saving && !context.switching_vault
|
||||
}
|
||||
@@ -288,6 +313,13 @@ pub fn enabled(action: UiAction, context: ActionContext) -> bool {
|
||||
&& !context.editing
|
||||
&& !context.switching_vault
|
||||
}
|
||||
UiAction::GeneratePassword => {
|
||||
context.unlocked
|
||||
&& context.editing
|
||||
&& context.focused_generatable
|
||||
&& !context.switching_vault
|
||||
&& !context.modal_open
|
||||
}
|
||||
UiAction::ToggleReveal => {
|
||||
context.unlocked
|
||||
&& context.document_open
|
||||
@@ -303,7 +335,25 @@ pub fn disabled_reason(action: UiAction, context: ActionContext) -> Option<&'sta
|
||||
return None;
|
||||
}
|
||||
Some(match action {
|
||||
UiAction::NewEntry => "Entry creation is not available yet",
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry
|
||||
if !context.storage_ready =>
|
||||
{
|
||||
"Shared configuration is unavailable"
|
||||
}
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry if context.dirty => {
|
||||
"Save or discard the current draft first"
|
||||
}
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry if context.saving => {
|
||||
"Wait for the active save"
|
||||
}
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry
|
||||
if context.switching_vault =>
|
||||
{
|
||||
"Wait for vault validation"
|
||||
}
|
||||
UiAction::InitializeStore | UiAction::NewFolder | UiAction::NewEntry => {
|
||||
"Close the current screen first"
|
||||
}
|
||||
UiAction::OpenFolder if !context.storage_ready => "Shared configuration is unavailable",
|
||||
UiAction::OpenFolder if context.saving => "Wait for the active save",
|
||||
UiAction::OpenFolder => "Wait for vault validation",
|
||||
@@ -345,6 +395,13 @@ pub fn disabled_reason(action: UiAction, context: ActionContext) -> Option<&'sta
|
||||
UiAction::EditEntry if !context.document_open => "Open an entry first",
|
||||
UiAction::EditEntry if context.editing => "The entry editor is already open",
|
||||
UiAction::EditEntry => "Wait for vault validation",
|
||||
UiAction::GeneratePassword if !context.unlocked => "Unlock an entry first",
|
||||
UiAction::GeneratePassword if !context.editing => "Open the entry editor first",
|
||||
UiAction::GeneratePassword if !context.focused_generatable => {
|
||||
"Select a password or other generatable secret field first"
|
||||
}
|
||||
UiAction::GeneratePassword if context.switching_vault => "Wait for vault validation",
|
||||
UiAction::GeneratePassword => "Close the current screen first",
|
||||
UiAction::ToggleReveal if !context.unlocked => "Unlock an entry first",
|
||||
UiAction::ToggleReveal if !context.document_open => "Open an entry first",
|
||||
UiAction::ToggleReveal if !context.focused_sensitive => "Select a sensitive field first",
|
||||
@@ -365,6 +422,8 @@ pub const fn aliases(action: UiAction) -> &'static [&'static str] {
|
||||
match action {
|
||||
UiAction::About => &["version", "license", "credits"],
|
||||
UiAction::Settings => &["preferences", "configuration", "config"],
|
||||
UiAction::InitializeStore => &["pass init", "recipients", "gpg id"],
|
||||
UiAction::NewFolder => &["create folder", "directory", "nested recipients"],
|
||||
UiAction::NewEntry => &["insert", "add password", "create entry"],
|
||||
UiAction::OpenFolder => &["open vault", "open store", "choose folder"],
|
||||
UiAction::OpenEntry => &["show entry", "view password"],
|
||||
@@ -382,6 +441,7 @@ pub const fn aliases(action: UiAction) -> &'static [&'static str] {
|
||||
UiAction::Refresh => &["reload vault", "refresh tree"],
|
||||
UiAction::ReloadEntry => &["revert entry", "refresh entry"],
|
||||
UiAction::EditEntry => &["modify entry"],
|
||||
UiAction::GeneratePassword => &["random password", "replace password", "generate"],
|
||||
UiAction::ToggleReveal => &["show password", "hide password", "reveal field"],
|
||||
UiAction::Lock => &["secure", "log out", "relock"],
|
||||
UiAction::Minimize => &["hide window"],
|
||||
@@ -439,6 +499,7 @@ mod tests {
|
||||
modal_open: false,
|
||||
focused_field: true,
|
||||
focused_sensitive: true,
|
||||
focused_generatable: true,
|
||||
entry_path: true,
|
||||
}
|
||||
}
|
||||
@@ -464,6 +525,7 @@ mod tests {
|
||||
assert!(!enabled(UiAction::NewEntry, ready));
|
||||
assert!(enabled(UiAction::Save, ready));
|
||||
assert!(enabled(UiAction::CopyEditedField, ready));
|
||||
assert!(enabled(UiAction::GeneratePassword, ready));
|
||||
assert!(!enabled(UiAction::CopyField, ready));
|
||||
assert!(enabled(UiAction::ToggleReveal, ready));
|
||||
|
||||
@@ -474,6 +536,9 @@ mod tests {
|
||||
};
|
||||
assert!(enabled(UiAction::CopyField, viewing));
|
||||
assert!(!enabled(UiAction::CopyEditedField, viewing));
|
||||
assert!(enabled(UiAction::InitializeStore, viewing));
|
||||
assert!(enabled(UiAction::NewFolder, viewing));
|
||||
assert!(enabled(UiAction::NewEntry, viewing));
|
||||
|
||||
let locked = ActionContext {
|
||||
unlocked: false,
|
||||
@@ -484,6 +549,7 @@ mod tests {
|
||||
UiAction::CopyField,
|
||||
UiAction::CopyEditedField,
|
||||
UiAction::ToggleReveal,
|
||||
UiAction::GeneratePassword,
|
||||
UiAction::Lock,
|
||||
] {
|
||||
assert!(!enabled(action, locked));
|
||||
@@ -523,6 +589,7 @@ mod tests {
|
||||
UiAction::Refresh,
|
||||
UiAction::ReloadEntry,
|
||||
UiAction::EditEntry,
|
||||
UiAction::GeneratePassword,
|
||||
UiAction::ToggleReveal,
|
||||
] {
|
||||
assert!(!enabled(action, switching), "{action:?}");
|
||||
|
||||
@@ -35,6 +35,21 @@ impl EntryEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_entry(
|
||||
document: EntryDocument,
|
||||
password: SecretBytes,
|
||||
) -> Result<Self, DocumentError> {
|
||||
let mut editor = Self::new(document);
|
||||
editor.add_after(None)?;
|
||||
let id = editor
|
||||
.focused
|
||||
.expect("new entry receives its password field");
|
||||
if !password.expose().is_empty() {
|
||||
editor.replace_value(id, password)?;
|
||||
}
|
||||
Ok(editor)
|
||||
}
|
||||
|
||||
pub fn entry(&self) -> String {
|
||||
self.document.path().to_string()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -130,12 +130,15 @@ fn accelerator(action: UiAction) -> Option<Accelerator> {
|
||||
UiAction::Lock => (command, Code::KeyL),
|
||||
UiAction::Help => (Modifiers::empty(), Code::F1),
|
||||
UiAction::About
|
||||
| UiAction::InitializeStore
|
||||
| UiAction::NewFolder
|
||||
| UiAction::CopyField
|
||||
| UiAction::CopyEditedField
|
||||
| UiAction::TogglePaneFocus
|
||||
| UiAction::OpenEntry
|
||||
| UiAction::ReloadEntry
|
||||
| UiAction::EditEntry
|
||||
| UiAction::GeneratePassword
|
||||
| UiAction::ToggleReveal
|
||||
| UiAction::Minimize => return None,
|
||||
};
|
||||
|
||||
@@ -156,6 +156,16 @@ mod tests {
|
||||
assert_eq!(matches("setings").first(), Some(&UiAction::Settings));
|
||||
assert_eq!(matches("rfsh").first(), Some(&UiAction::Refresh));
|
||||
assert_eq!(matches("open vault").first(), Some(&UiAction::OpenFolder));
|
||||
assert_eq!(
|
||||
matches("pass init").first(),
|
||||
Some(&UiAction::InitializeStore)
|
||||
);
|
||||
assert_eq!(matches("create folder").first(), Some(&UiAction::NewFolder));
|
||||
assert_eq!(matches("insert").first(), Some(&UiAction::NewEntry));
|
||||
assert_eq!(
|
||||
matches("generate").first(),
|
||||
Some(&UiAction::GeneratePassword)
|
||||
);
|
||||
assert_eq!(
|
||||
matches(""),
|
||||
action::ACTIONS
|
||||
|
||||
Reference in New Issue
Block a user