mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-05 17:15:12 +00:00
make table responsive
This commit is contained in:
parent
c3d5559234
commit
60af11bd47
|
@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Write new email [#8]
|
- Write new email [#8]
|
||||||
- Reply, reply all and forward [#9] [#10] [#11]
|
- Reply, reply all and forward [#9] [#10] [#11]
|
||||||
- Download attachments [#14]
|
- Download attachments [#14]
|
||||||
|
- Merge `Email` with `Msg` [#21]
|
||||||
|
- List command with pagination [#19]
|
||||||
|
- Icon in table when attachment is present [#16]
|
||||||
|
|
||||||
[unreleased]: https://github.com/soywod/himalaya
|
[unreleased]: https://github.com/soywod/himalaya
|
||||||
|
|
||||||
|
@ -37,3 +40,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
[#13]: https://github.com/soywod/himalaya/issues/13
|
[#13]: https://github.com/soywod/himalaya/issues/13
|
||||||
[#14]: https://github.com/soywod/himalaya/issues/14
|
[#14]: https://github.com/soywod/himalaya/issues/14
|
||||||
[#15]: https://github.com/soywod/himalaya/issues/15
|
[#15]: https://github.com/soywod/himalaya/issues/15
|
||||||
|
[#16]: https://github.com/soywod/himalaya/issues/16
|
||||||
|
[#19]: https://github.com/soywod/himalaya/issues/19
|
||||||
|
[#21]: https://github.com/soywod/himalaya/issues/21
|
||||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -280,6 +280,7 @@ dependencies = [
|
||||||
"rfc2047-decoder",
|
"rfc2047-decoder",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
|
"terminal_size",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -946,6 +947,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "terminal_size"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bd2d183bd3fac5f5fe38ddbeb4dc9aec4a39a9d7d59e7491d900302da01cbe1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
|
@ -14,4 +14,5 @@ native-tls = "0.2"
|
||||||
rfc2047-decoder = "0.1.2"
|
rfc2047-decoder = "0.1.2"
|
||||||
rustyline = "7.1.0"
|
rustyline = "7.1.0"
|
||||||
serde = { version = "1.0.118", features = ["derive"] }
|
serde = { version = "1.0.118", features = ["derive"] }
|
||||||
|
terminal_size = "0.1.15"
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
|
|
18
src/mbox.rs
18
src/mbox.rs
|
@ -20,17 +20,25 @@ impl Mbox {
|
||||||
|
|
||||||
impl DisplayRow for Mbox {
|
impl DisplayRow for Mbox {
|
||||||
fn to_row(&self) -> Vec<table::Cell> {
|
fn to_row(&self) -> Vec<table::Cell> {
|
||||||
|
use crate::table::*;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
table::Cell::new(&[table::BLUE], &self.delim),
|
Cell::new(&[BLUE], &self.delim),
|
||||||
table::Cell::new(&[table::GREEN], &self.name),
|
Cell::new(&[GREEN], &self.name),
|
||||||
table::Cell::new(&[table::YELLOW], &self.attributes.join(", ")),
|
FlexCell::new(&[YELLOW], &self.attributes.join(", ")),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DisplayTable<'a, Mbox> for Vec<Mbox> {
|
impl<'a> DisplayTable<'a, Mbox> for Vec<Mbox> {
|
||||||
fn cols() -> &'a [&'a str] {
|
fn header_row() -> Vec<table::Cell> {
|
||||||
&["delim", "name", "attributes"]
|
use crate::table::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "DELIM"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "NAME"),
|
||||||
|
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "ATTRIBUTES"),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows(&self) -> &Vec<Mbox> {
|
fn rows(&self) -> &Vec<Mbox> {
|
||||||
|
|
35
src/msg.rs
35
src/msg.rs
|
@ -379,7 +379,10 @@ impl DisplayRow for Msg {
|
||||||
let headers = parsed.get_headers();
|
let headers = parsed.get_headers();
|
||||||
|
|
||||||
let uid = &self.uid.to_string();
|
let uid = &self.uid.to_string();
|
||||||
let flags = String::new(); // TODO: render flags
|
let flags = match self.extract_attachments().map(|vec| vec.is_empty()) {
|
||||||
|
Ok(false) => "",
|
||||||
|
_ => " ",
|
||||||
|
};
|
||||||
let sender = headers
|
let sender = headers
|
||||||
.get_first_value("reply-to")
|
.get_first_value("reply-to")
|
||||||
.or(headers.get_first_value("from"))
|
.or(headers.get_first_value("from"))
|
||||||
|
@ -387,21 +390,33 @@ impl DisplayRow for Msg {
|
||||||
let subject = headers.get_first_value("subject").unwrap_or_default();
|
let subject = headers.get_first_value("subject").unwrap_or_default();
|
||||||
let date = headers.get_first_value("date").unwrap_or_default();
|
let date = headers.get_first_value("date").unwrap_or_default();
|
||||||
|
|
||||||
vec![
|
{
|
||||||
table::Cell::new(&[table::RED], &uid),
|
use crate::table::*;
|
||||||
table::Cell::new(&[table::WHITE], &flags),
|
|
||||||
table::Cell::new(&[table::BLUE], &sender),
|
vec![
|
||||||
table::Cell::new(&[table::GREEN], &subject),
|
Cell::new(&[RED], &uid),
|
||||||
table::Cell::new(&[table::YELLOW], &date),
|
Cell::new(&[WHITE], &flags),
|
||||||
]
|
Cell::new(&[BLUE], &sender),
|
||||||
|
FlexCell::new(&[GREEN], &subject),
|
||||||
|
Cell::new(&[YELLOW], &date),
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
|
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
|
||||||
fn cols() -> &'a [&'a str] {
|
fn header_row() -> Vec<table::Cell> {
|
||||||
&["uid", "flags", "sender", "subject", "date"]
|
use crate::table::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "FLAGS"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"),
|
||||||
|
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows(&self) -> &Vec<Msg> {
|
fn rows(&self) -> &Vec<Msg> {
|
||||||
|
|
85
src/table.rs
85
src/table.rs
|
@ -1,4 +1,5 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use terminal_size::terminal_size;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Style(u8, u8, u8);
|
pub struct Style(u8, u8, u8);
|
||||||
|
@ -33,13 +34,15 @@ impl fmt::Display for Style {
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
pub styles: Vec<Style>,
|
pub styles: Vec<Style>,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
|
pub flex: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
pub fn new(styles: &[Style], value: &str) -> Cell {
|
pub fn new(styles: &[Style], value: &str) -> Self {
|
||||||
Cell {
|
Self {
|
||||||
styles: styles.to_vec(),
|
styles: styles.to_vec(),
|
||||||
value: value.to_string(),
|
value: value.trim().to_string(),
|
||||||
|
flex: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,20 +51,45 @@ impl Cell {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, col_size: usize) -> String {
|
pub fn render(&self, col_size: usize) -> String {
|
||||||
let style_start = self
|
let style_begin = self
|
||||||
.styles
|
.styles
|
||||||
.iter()
|
.iter()
|
||||||
.map(|style| format!("{}", style))
|
.map(|style| style.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.concat();
|
.concat();
|
||||||
|
let style_end = "\x1b[0m";
|
||||||
|
|
||||||
let padding = if col_size == 0 {
|
if col_size > 0 && self.printable_value_len() > col_size {
|
||||||
"".to_string()
|
let col_size = self
|
||||||
|
.value
|
||||||
|
.char_indices()
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.nth(col_size)
|
||||||
|
.unwrap()
|
||||||
|
- 2;
|
||||||
|
|
||||||
|
String::from(style_begin + &self.value[0..=col_size] + "… " + style_end)
|
||||||
} else {
|
} else {
|
||||||
" ".repeat(col_size - self.printable_value_len() + 1)
|
let padding = if col_size == 0 {
|
||||||
};
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
" ".repeat(col_size - self.printable_value_len() + 1)
|
||||||
|
};
|
||||||
|
|
||||||
String::from(style_start + &self.value + &padding + "\x1b[0m")
|
String::from(style_begin + &self.value + &padding + style_end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FlexCell;
|
||||||
|
|
||||||
|
impl FlexCell {
|
||||||
|
pub fn new(styles: &[Style], value: &str) -> Cell {
|
||||||
|
Cell {
|
||||||
|
flex: true,
|
||||||
|
..Cell::new(styles, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,22 +98,18 @@ pub trait DisplayRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DisplayTable<'a, T: DisplayRow + 'a> {
|
pub trait DisplayTable<'a, T: DisplayRow + 'a> {
|
||||||
fn cols() -> &'a [&'a str];
|
fn header_row() -> Vec<Cell>;
|
||||||
fn rows(&self) -> &Vec<T>;
|
fn rows(&self) -> &Vec<T>;
|
||||||
|
|
||||||
fn to_table(&self) -> String {
|
fn to_table(&self) -> String {
|
||||||
let mut col_sizes = vec![];
|
let mut col_sizes = vec![];
|
||||||
|
let head = Self::header_row();
|
||||||
|
|
||||||
let head = Self::cols()
|
head.iter().for_each(|cell| {
|
||||||
.iter()
|
col_sizes.push(cell.printable_value_len());
|
||||||
.map(|col| {
|
});
|
||||||
let cell = Cell::new(&[BOLD, UNDERLINE, WHITE], &col.to_uppercase());
|
|
||||||
col_sizes.push(cell.printable_value_len());
|
|
||||||
cell
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut body = self
|
let mut table = self
|
||||||
.rows()
|
.rows()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| {
|
.map(|item| {
|
||||||
|
@ -97,13 +121,28 @@ pub trait DisplayTable<'a, T: DisplayRow + 'a> {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
body.insert(0, head);
|
table.insert(0, head);
|
||||||
|
|
||||||
body.iter().fold(String::new(), |output, row| {
|
let term_width = terminal_size().map(|size| size.0 .0).unwrap_or(0) as usize;
|
||||||
|
let seps_width = 2 * col_sizes.len() - 1;
|
||||||
|
let table_width = col_sizes.iter().sum::<usize>() + seps_width;
|
||||||
|
let diff_width = if table_width < term_width {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
table_width - term_width
|
||||||
|
};
|
||||||
|
|
||||||
|
table.iter().fold(String::new(), |output, row| {
|
||||||
let row_str = row
|
let row_str = row
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, cell)| cell.render(col_sizes[i]))
|
.map(|(i, cell)| {
|
||||||
|
if cell.flex && col_sizes[i] > diff_width {
|
||||||
|
cell.render(col_sizes[i] - diff_width)
|
||||||
|
} else {
|
||||||
|
cell.render(col_sizes[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(&Cell::new(&[ext(8)], "|").render(0));
|
.join(&Cell::new(&[ext(8)], "|").render(0));
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue