Browse Source

ui/compose: add modification detection

Detect if modifications were done to the draft in the compose tab so
that we can ask for confirmation if user wants to quit an unsaved draft.
embed
Manos Pitsidianakis 3 years ago
parent
commit
1a02491f04
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 15
      Cargo.lock
  2. 2
      melib/src/email/address.rs
  3. 10
      melib/src/email/attachment_types.rs
  4. 4
      melib/src/email/attachments.rs
  5. 2
      melib/src/email/compose.rs
  6. 78
      ui/src/components/mail/compose.rs

15
Cargo.lock

@ -645,18 +645,6 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nix"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.13"
@ -1155,7 +1143,6 @@ version = "0.3.2"
dependencies = [
"melib 0.3.2",
"text_processing 0.3.2",
"ui 0.3.2",
]
[[package]]
@ -1194,7 +1181,6 @@ dependencies = [
"linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"melib 0.3.2",
"mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)",
"nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
"notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1372,7 +1358,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b"
"checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943"

2
melib/src/email/address.rs

@ -128,7 +128,7 @@ impl fmt::Debug for Address {
}
/// Helper struct to return slices from a struct field on demand.
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Copy)]
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Copy)]
pub struct StrBuilder {
pub offset: usize,
pub length: usize,

10
melib/src/email/attachment_types.rs

@ -24,7 +24,7 @@ use crate::email::parser::BytesExt;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Charset {
Ascii,
UTF8,
@ -95,7 +95,7 @@ impl Display for Charset {
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MultipartType {
Mixed,
Alternative,
@ -136,7 +136,7 @@ impl From<&[u8]> for MultipartType {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ContentType {
Text {
kind: Text,
@ -245,7 +245,7 @@ impl ContentType {
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Text {
Plain,
Html,
@ -274,7 +274,7 @@ impl Display for Text {
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ContentTransferEncoding {
_8Bit,
_7Bit,

4
melib/src/email/attachments.rs

@ -28,7 +28,7 @@ use data_encoding::BASE64_MIME;
pub use crate::email::attachment_types::*;
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AttachmentBuilder {
pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding,
@ -291,7 +291,7 @@ impl From<AttachmentBuilder> for Attachment {
}
/// Immutable attachment type.
#[derive(Clone, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Attachment {
pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding,

2
melib/src/email/compose.rs

@ -17,7 +17,7 @@ pub mod random;
use super::parser;
use fnv::FnvHashMap;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Draft {
pub headers: FnvHashMap<String, String>,
pub header_order: Vec<String>,

78
ui/src/components/mail/compose.rs

@ -46,6 +46,7 @@ pub struct Composer {
mode: ViewMode,
sign_mail: ToggleFlag,
dirty: bool,
has_changes: bool,
initialized: bool,
id: ComponentId,
}
@ -65,6 +66,7 @@ impl Default for Composer {
mode: ViewMode::Edit,
sign_mail: ToggleFlag::Unset,
dirty: true,
has_changes: false,
initialized: false,
id: ComponentId::new_v4(),
}
@ -246,6 +248,7 @@ impl Composer {
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
let attachments_no = self.draft.attachments().len();
clear_area(grid, area);
if self.sign_mail.is_true() {
write_string_to_grid(
&format!(
@ -331,7 +334,6 @@ impl Composer {
impl Component for Composer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area);
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
@ -459,6 +461,7 @@ impl Component for Composer {
),
false,
);
clear_area(grid, ((x, y), (set_y(bottom_right, y))));
change_colors(
grid,
(
@ -468,28 +471,63 @@ impl Component for Composer {
Color::Byte(189),
Color::Byte(167),
);
if mid != 0 {
clear_area(
grid,
(
pos_dec(upper_left, (0, 1)),
set_x(bottom_right, get_x(upper_left) + mid),
),
);
clear_area(
grid,
(
(
get_x(bottom_right).saturating_sub(mid),
get_y(upper_left) - 1,
),
bottom_right,
),
);
}
/* Regardless of view mode, do the following */
self.form.draw(grid, header_area, context);
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
if self.cursor == Cursor::Body {
change_colors(
grid,
(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
),
Color::Default,
Color::Byte(237),
);
} else {
change_colors(
grid,
(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
),
Color::Default,
Color::Default,
);
}
match self.mode {
ViewMode::ThreadView | ViewMode::Edit => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
}
ViewMode::ThreadView | ViewMode::Edit => {}
ViewMode::SelectRecipients(ref mut s) => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
s.draw(grid, center_area(area, s.content.size()), context);
}
ViewMode::Discard(_, ref mut s) => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
/* Let user choose whether to quit with/without saving or cancel */
s.draw(grid, center_area(area, s.content.size()), context);
}
}
self.dirty = false;
self.draw_attachments(grid, attachment_area, context);
context.dirty_areas.push_back(area);
}
@ -626,7 +664,10 @@ impl Component for Composer {
}
_ => {}
}
if self.form.process_event(event, context) {
if self.cursor == Cursor::Headers && self.form.process_event(event, context) {
if let UIEvent::InsertInput(_) = event {
self.has_changes = true;
}
return true;
}
@ -659,9 +700,12 @@ impl Component for Composer {
}*/
UIEvent::Input(Key::Up) => {
self.cursor = Cursor::Headers;
self.form.process_event(event, context);
self.dirty = true;
}
UIEvent::Input(Key::Down) => {
self.cursor = Cursor::Body;
self.dirty = true;
}
/* Switch to thread view mode if we're on Edit mode */
UIEvent::Input(Key::Char('v')) if self.mode.is_edit() => {
@ -742,6 +786,9 @@ impl Component for Composer {
let result = f.read_to_string();
let mut new_draft = Draft::from_str(result.as_str()).unwrap();
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
if self.draft != new_draft {
self.has_changes = true;
}
self.draft = new_draft;
self.initialized = false;
context.replies.push_back(UIEvent::Fork(ForkType::Finished));
@ -828,6 +875,11 @@ impl Component for Composer {
}
fn kill(&mut self, uuid: Uuid, context: &mut Context) {
if !self.has_changes {
context.replies.push_back(UIEvent::Action(Tab(Kill(uuid))));
return;
}
self.mode = ViewMode::Discard(
uuid,
Selector::new(
@ -876,6 +928,10 @@ impl Component for Composer {
}
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
if !self.has_changes {
return true;
}
/* Play it safe and ask user for confirmation */
self.mode = ViewMode::Discard(
self.id,

Loading…
Cancel
Save