Refine desktop entry field layout
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use iced::widget::text_editor;
|
||||
use ironstorage::{
|
||||
document::{DocumentError, EntryDocument, EntryField, EntryFieldDraft, EntryFieldId},
|
||||
repository::SecretBytes,
|
||||
@@ -10,6 +11,7 @@ use ironstorage::{
|
||||
pub struct EntryEditor {
|
||||
document: EntryDocument,
|
||||
focused: Option<EntryFieldId>,
|
||||
multiline: Vec<(EntryFieldId, text_editor::Content)>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
@@ -24,9 +26,20 @@ pub enum FieldNavigation {
|
||||
impl EntryEditor {
|
||||
pub fn new(document: EntryDocument) -> Self {
|
||||
let focused = document.fields().first().map(EntryField::id);
|
||||
let multiline = document
|
||||
.fields()
|
||||
.iter()
|
||||
.filter_map(|field| {
|
||||
let value = std::str::from_utf8(field.value()).ok()?;
|
||||
value
|
||||
.contains('\n')
|
||||
.then(|| (field.id(), text_editor::Content::with_text(value)))
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
document,
|
||||
focused,
|
||||
multiline,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
@@ -66,6 +79,12 @@ impl EntryEditor {
|
||||
self.focused
|
||||
}
|
||||
|
||||
pub fn multiline_content(&self, id: EntryFieldId) -> Option<&text_editor::Content> {
|
||||
self.multiline
|
||||
.iter()
|
||||
.find_map(|(field_id, content)| (*field_id == id).then_some(content))
|
||||
}
|
||||
|
||||
pub fn focused_ratio(&self) -> f32 {
|
||||
let fields = self.document.fields();
|
||||
self.focused
|
||||
@@ -81,6 +100,33 @@ impl EntryEditor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn edit_multiline(
|
||||
&mut self,
|
||||
id: EntryFieldId,
|
||||
action: text_editor::Action,
|
||||
) -> Result<(), DocumentError> {
|
||||
let previous = self
|
||||
.document
|
||||
.field(id)
|
||||
.ok_or(DocumentError::UnknownField { id })?
|
||||
.value();
|
||||
let value = {
|
||||
let content = self
|
||||
.multiline
|
||||
.iter_mut()
|
||||
.find_map(|(field_id, content)| (*field_id == id).then_some(content))
|
||||
.ok_or(DocumentError::UnknownField { id })?;
|
||||
content.perform(action);
|
||||
content.text().into_bytes()
|
||||
};
|
||||
self.focused = Some(id);
|
||||
if value != previous {
|
||||
self.document.replace_field_value(id, value)?;
|
||||
self.dirty = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn navigate(&mut self, navigation: FieldNavigation) {
|
||||
let fields = self.document.fields();
|
||||
if fields.is_empty() {
|
||||
@@ -111,25 +157,7 @@ impl EntryEditor {
|
||||
}
|
||||
self.document
|
||||
.update(id, EntryFieldDraft::multiline(value.to_vec()))?;
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_value_line(
|
||||
&mut self,
|
||||
id: EntryFieldId,
|
||||
line: usize,
|
||||
replacement: &[u8],
|
||||
) -> Result<(), DocumentError> {
|
||||
let field = self
|
||||
.document
|
||||
.field(id)
|
||||
.ok_or(DocumentError::UnknownField { id })?;
|
||||
let mut value = field.value().to_vec();
|
||||
let range =
|
||||
value_line_range(&value, line).ok_or(DocumentError::InvalidIndex { index: line })?;
|
||||
value.splice(range, replacement.iter().copied());
|
||||
self.document.replace_field_value(id, value)?;
|
||||
self.sync_multiline(id);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -148,6 +176,7 @@ impl EntryEditor {
|
||||
value_line_end(&value, line).ok_or(DocumentError::InvalidIndex { index: line })?;
|
||||
value.splice(insertion..insertion, *b"\n");
|
||||
self.document.replace_field_value(id, value)?;
|
||||
self.sync_multiline(id);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -174,6 +203,7 @@ impl EntryEditor {
|
||||
.position(|field| field.id() == id)
|
||||
.ok_or(DocumentError::UnknownField { id })?;
|
||||
self.document.remove(id)?;
|
||||
self.multiline.retain(|(field_id, _)| *field_id != id);
|
||||
if self.focused == Some(id) {
|
||||
self.focused = self
|
||||
.document
|
||||
@@ -224,6 +254,7 @@ impl EntryEditor {
|
||||
) -> Result<(), DocumentError> {
|
||||
self.document
|
||||
.replace_field_value(id, value.expose().to_vec())?;
|
||||
self.sync_multiline(id);
|
||||
self.dirty = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -231,24 +262,30 @@ impl EntryEditor {
|
||||
pub fn copy_value(&self, id: EntryFieldId) -> Result<SecretBytes, DocumentError> {
|
||||
self.document.copy_field_value(id)
|
||||
}
|
||||
}
|
||||
|
||||
fn value_line_range(value: &[u8], target: usize) -> Option<std::ops::Range<usize>> {
|
||||
let mut start = 0;
|
||||
let mut lines = 0;
|
||||
for (line, segment) in value.split_inclusive(|byte| *byte == b'\n').enumerate() {
|
||||
lines = line + 1;
|
||||
let mut end = start + segment.len() - usize::from(segment.ends_with(b"\n"));
|
||||
if end > start && value[end - 1] == b'\r' {
|
||||
end -= 1;
|
||||
fn sync_multiline(&mut self, id: EntryFieldId) {
|
||||
let value = self
|
||||
.document
|
||||
.field(id)
|
||||
.and_then(|field| std::str::from_utf8(field.value()).ok())
|
||||
.filter(|value| value.contains('\n'));
|
||||
let existing = self
|
||||
.multiline
|
||||
.iter()
|
||||
.position(|(field_id, _)| *field_id == id);
|
||||
match (existing, value) {
|
||||
(Some(index), Some(value)) => {
|
||||
self.multiline[index].1 = text_editor::Content::with_text(value);
|
||||
}
|
||||
(None, Some(value)) => self
|
||||
.multiline
|
||||
.push((id, text_editor::Content::with_text(value))),
|
||||
(Some(index), None) => {
|
||||
self.multiline.remove(index);
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
if line == target {
|
||||
return Some(start..end);
|
||||
}
|
||||
start += segment.len();
|
||||
}
|
||||
(target == lines && (value.is_empty() || value.ends_with(b"\n")))
|
||||
.then_some(value.len()..value.len())
|
||||
}
|
||||
|
||||
fn value_line_end(value: &[u8], target: usize) -> Option<usize> {
|
||||
|
||||
Reference in New Issue
Block a user