Implement structured desktop editing
This commit is contained in:
@@ -13,3 +13,7 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
iced.workspace = true
|
||||
ironstorage.workspace = true
|
||||
zeroize.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
154
apps/desktop/src/editor.rs
Normal file
154
apps/desktop/src/editor.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
//! Structured desktop editing state over storage-owned entry documents.
|
||||
|
||||
use std::{collections::BTreeSet, fmt};
|
||||
|
||||
use ironstorage::{
|
||||
document::{
|
||||
DocumentError, EntryDocument, EntryField, EntryFieldDraft, EntryFieldId, EntrySensitivity,
|
||||
},
|
||||
repository::SecretBytes,
|
||||
};
|
||||
|
||||
pub struct EntryEditor {
|
||||
document: EntryDocument,
|
||||
revealed: BTreeSet<EntryFieldId>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl EntryEditor {
|
||||
pub fn new(document: EntryDocument) -> Self {
|
||||
Self {
|
||||
document,
|
||||
revealed: BTreeSet::new(),
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entry(&self) -> String {
|
||||
self.document.path().to_string()
|
||||
}
|
||||
|
||||
pub fn document(&self) -> &EntryDocument {
|
||||
&self.document
|
||||
}
|
||||
|
||||
pub fn fields(&self) -> &[EntryField] {
|
||||
self.document.fields()
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
pub fn is_revealed(&self, id: EntryFieldId) -> bool {
|
||||
self.revealed.contains(&id)
|
||||
}
|
||||
|
||||
pub fn toggle_reveal(&mut self, id: EntryFieldId) -> Result<(), DocumentError> {
|
||||
let field = self
|
||||
.document
|
||||
.field(id)
|
||||
.ok_or(DocumentError::UnknownField { id })?;
|
||||
if field.metadata().sensitivity() != EntrySensitivity::Sensitive {
|
||||
return Ok(());
|
||||
}
|
||||
if !self.revealed.remove(&id) {
|
||||
self.revealed.insert(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_raw(&mut self, id: EntryFieldId, value: &[u8]) -> Result<(), DocumentError> {
|
||||
let unchanged = self
|
||||
.document
|
||||
.field(id)
|
||||
.is_some_and(|field| field.contents().expose() == value);
|
||||
if unchanged {
|
||||
return Ok(());
|
||||
}
|
||||
self.document
|
||||
.update(id, EntryFieldDraft::line(value.to_vec())?)?;
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_after(&mut self, id: Option<EntryFieldId>) -> Result<(), DocumentError> {
|
||||
let index = id
|
||||
.and_then(|id| {
|
||||
self.document
|
||||
.fields()
|
||||
.iter()
|
||||
.position(|field| field.id() == id)
|
||||
})
|
||||
.map_or(self.document.fields().len(), |index| index + 1);
|
||||
self.document.add(index, EntryFieldDraft::blank())?;
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, id: EntryFieldId) -> Result<(), DocumentError> {
|
||||
self.document.remove(id)?;
|
||||
self.revealed.remove(&id);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_up(&mut self, id: EntryFieldId) -> Result<(), DocumentError> {
|
||||
let Some(index) = self
|
||||
.document
|
||||
.fields()
|
||||
.iter()
|
||||
.position(|field| field.id() == id)
|
||||
else {
|
||||
return Err(DocumentError::UnknownField { id });
|
||||
};
|
||||
if index > 0 {
|
||||
self.document.reorder(id, index - 1)?;
|
||||
self.dirty = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn move_down(&mut self, id: EntryFieldId) -> Result<(), DocumentError> {
|
||||
let Some(index) = self
|
||||
.document
|
||||
.fields()
|
||||
.iter()
|
||||
.position(|field| field.id() == id)
|
||||
else {
|
||||
return Err(DocumentError::UnknownField { id });
|
||||
};
|
||||
if index + 1 < self.document.fields().len() {
|
||||
self.document.reorder(id, index + 1)?;
|
||||
self.dirty = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace_value(
|
||||
&mut self,
|
||||
id: EntryFieldId,
|
||||
value: SecretBytes,
|
||||
) -> Result<(), DocumentError> {
|
||||
self.document
|
||||
.replace_field_value(id, value.expose().to_vec())?;
|
||||
self.revealed.remove(&id);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_value(&self, id: EntryFieldId) -> Result<SecretBytes, DocumentError> {
|
||||
self.document.copy_field_value(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for EntryEditor {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("EntryEditor")
|
||||
.field("document", &self.document)
|
||||
.field("revealed", &self.revealed)
|
||||
.field("dirty", &self.dirty)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user