From 831ec97d64ef7aed32f950586cbe20924010af16 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 5 Jun 2019 01:24:17 +0300 Subject: ui: add columns in PlainListing --- ui/src/components/mail/listing/plain.rs | 189 ++++++++++++++++++++++++++------ 1 file changed, 156 insertions(+), 33 deletions(-) diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs index 83c9eeaf..e5758356 100644 --- a/ui/src/components/mail/listing/plain.rs +++ b/ui/src/components/mail/listing/plain.rs @@ -21,7 +21,48 @@ use super::*; +use std::cmp; +use std::ops::{Deref, DerefMut}; const MAX_COLS: usize = 500; +macro_rules! address_list { + (($name:expr) as comma_sep_list) => {{ + let mut ret: String = + $name + .into_iter() + .fold(String::new(), |mut s: String, n: &Address| { + s.extend(n.to_string().chars()); + s.push_str(", "); + s + }); + ret.pop(); + ret.pop(); + ret + }}; +} + +macro_rules! column_str { + ( + struct $name:ident(String)) => { + pub struct $name(String); + + impl Deref for $name { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + impl DerefMut for $name { + fn deref_mut(&mut self) -> &mut String { + &mut self.0 + } + } + }; +} + +column_str!(struct IndexNoString(String)); +column_str!(struct DateString(String)); +column_str!(struct FromString(String)); +column_str!(struct SubjectString(String)); /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// `MailView`. @@ -69,12 +110,19 @@ impl fmt::Display for PlainListing { impl PlainListing { /// Helper function to format entry strings for PlainListing */ /* TODO: Make this configurable */ - fn make_entry_string(e: &Envelope, idx: usize) -> String { - format!( - "{} {} {}", - idx, - &e.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), - e.subject() + fn make_entry_string( + e: &Envelope, + idx: usize, + ) -> (IndexNoString, FromString, DateString, SubjectString) { + ( + IndexNoString(idx.to_string()), + FromString(address_list!((e.from()) as comma_sep_list)), + DateString(PlainListing::format_date(e)), + SubjectString(format!( + "{}{}", + e.subject(), + if e.has_attachments() { " 📎" } else { "" }, + )), ) } @@ -147,7 +195,56 @@ impl PlainListing { ); return; } + self.local_collection = account.collection.keys().cloned().collect(); + let sort = self.sort; + self.local_collection.sort_by(|a, b| match sort { + (SortField::Date, SortOrder::Desc) => { + let ma = &account.get_env(a); + let mb = &account.get_env(b); + mb.date().cmp(&ma.date()) + } + (SortField::Date, SortOrder::Asc) => { + let ma = &account.get_env(a); + let mb = &account.get_env(b); + ma.date().cmp(&mb.date()) + } + (SortField::Subject, SortOrder::Desc) => { + let ma = &account.get_env(a); + let mb = &account.get_env(b); + ma.subject().cmp(&mb.subject()) + } + (SortField::Subject, SortOrder::Asc) => { + let ma = &account.get_env(a); + let mb = &account.get_env(b); + mb.subject().cmp(&ma.subject()) + } + }); + + let mut rows = Vec::with_capacity(1024); + let mut min_width = (0, 0, 0); + let widths: (usize, usize, usize); + + for idx in 0..self.local_collection.len() { + let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); + let strings = PlainListing::make_entry_string(envelope, idx); + min_width.0 = cmp::max(min_width.0, strings.0.len()); /* index */ + min_width.1 = cmp::max(min_width.1, strings.2.split_graphemes().len()); /* date */ + min_width.2 = cmp::max(min_width.2, strings.3.split_graphemes().len()); /* subject */ + rows.push(strings); + } + let column_sep: usize = if MAX_COLS >= min_width.0 + min_width.1 + min_width.2 { + widths = min_width; + 2 + } else { + let width = MAX_COLS - 3 - min_width.0; + widths = ( + min_width.0, + cmp::min(min_width.1, width / 3), + cmp::min(min_width.2, width / 3), + ); + 1 + }; // Populate `CellBuffer` with every entry. let mut idx = 0; for y in 0..=self.length { @@ -157,30 +254,6 @@ impl PlainListing { break; } /* Write an entire line for each envelope entry. */ - self.local_collection = account.collection.keys().cloned().collect(); - let sort = self.sort; - self.local_collection.sort_by(|a, b| match sort { - (SortField::Date, SortOrder::Desc) => { - let ma = &account.get_env(a); - let mb = &account.get_env(b); - mb.date().cmp(&ma.date()) - } - (SortField::Date, SortOrder::Asc) => { - let ma = &account.get_env(a); - let mb = &account.get_env(b); - ma.date().cmp(&mb.date()) - } - (SortField::Subject, SortOrder::Desc) => { - let ma = &account.get_env(a); - let mb = &account.get_env(b); - ma.subject().cmp(&mb.subject()) - } - (SortField::Subject, SortOrder::Asc) => { - let ma = &account.get_env(a); - let mb = &account.get_env(b); - mb.subject().cmp(&ma.subject()) - } - }); let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); let fg_color = if !envelope.is_seen() { @@ -195,12 +268,48 @@ impl PlainListing { } else { Color::Default }; - let (x, y) = write_string_to_grid( - &PlainListing::make_entry_string(envelope, idx), + let (x, _) = write_string_to_grid( + &rows[idx].0, + &mut self.content, + fg_color, + bg_color, + ((0, idx), (widths.0, idx)), + false, + ); + for x in x..=widths.0 + column_sep { + self.content[(x, idx)].set_bg(bg_color); + } + let mut _x = widths.0 + column_sep; + let (mut x, _) = write_string_to_grid( + &rows[idx].2, + &mut self.content, + fg_color, + bg_color, + ((_x, idx), (widths.1 + _x, idx)), + false, + ); + _x += widths.1 + column_sep + 1; + for x in x.._x { + self.content[(x, idx)].set_bg(bg_color); + } + let (x, _) = write_string_to_grid( + &rows[idx].1, &mut self.content, fg_color, bg_color, - ((0, y), (MAX_COLS - 1, y)), + ((_x, idx), (widths.1 + _x, idx)), + false, + ); + _x += widths.1 + column_sep + 2; + for x in x.._x { + self.content[(x, idx)].set_bg(bg_color); + } + let (x, _) = write_string_to_grid( + &rows[idx].3, + &mut self.content, + fg_color, + bg_color, + ((_x, idx), (widths.2 + _x, idx)), false, ); @@ -328,6 +437,20 @@ impl PlainListing { ); context.dirty_areas.push_back(area); } + fn format_date(envelope: &Envelope) -> String { + let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); + let now: std::time::Duration = std::time::SystemTime::now() + .duration_since(d) + .unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0)); + match now.as_secs() { + n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)), + n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)), + n if n < 4 * 24 * 60 * 60 => { + format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9)) + } + _ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(), + } + } } impl Component for PlainListing { -- cgit v1.2.3