Implement structured desktop editing

This commit is contained in:
2026-08-10 15:45:25 +02:00
parent b9034c5274
commit 49451bc988
4 changed files with 1065 additions and 146 deletions

2
Cargo.lock generated
View File

@@ -4037,6 +4037,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"iced", "iced",
"ironstorage", "ironstorage",
"tempfile",
"zeroize",
] ]
[[package]] [[package]]

View File

@@ -13,3 +13,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
iced.workspace = true iced.workspace = true
ironstorage.workspace = true ironstorage.workspace = true
zeroize.workspace = true
[dev-dependencies]
tempfile = "3"

154
apps/desktop/src/editor.rs Normal file
View 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