ui: add contacts and account panel
parent
07a51de0b6
commit
92bb3bf8d3
|
@ -7,7 +7,7 @@ workspace = ".."
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
chan = "0.1.21"
|
chan = "0.1.21"
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
crossbeam = "^0.3.0"
|
crossbeam = "^0.3.0"
|
||||||
data-encoding = "2.1.1"
|
data-encoding = "2.1.1"
|
||||||
encoding = "0.2.33"
|
encoding = "0.2.33"
|
||||||
|
@ -21,3 +21,4 @@ xdg = "2.1.0"
|
||||||
serde = "1.0.71"
|
serde = "1.0.71"
|
||||||
serde_derive = "1.0.71"
|
serde_derive = "1.0.71"
|
||||||
bincode = "1.0.1"
|
bincode = "1.0.1"
|
||||||
|
uuid = { version = "0.6", features = ["serde", "v4"] }
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* meli - addressbook module
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use fnv::FnvHashMap;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct AddressBook {
|
||||||
|
display_name: String,
|
||||||
|
created: DateTime<Local>,
|
||||||
|
last_edited: DateTime<Local>,
|
||||||
|
cards: FnvHashMap<Uuid, Card>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct Card {
|
||||||
|
uuid: Uuid,
|
||||||
|
title: String,
|
||||||
|
firstname: String,
|
||||||
|
lastname: String,
|
||||||
|
additionalname: String,
|
||||||
|
name_prefix: String,
|
||||||
|
name_suffix: String,
|
||||||
|
//address
|
||||||
|
|
||||||
|
birthday: Option<DateTime<Local>>,
|
||||||
|
email: String,
|
||||||
|
url: String,
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
last_edited: DateTime<Local>,
|
||||||
|
extra_properties: FnvHashMap<String, String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressBook {
|
||||||
|
pub fn new(display_name: String) -> AddressBook {
|
||||||
|
AddressBook {
|
||||||
|
display_name,
|
||||||
|
created: Local::now(),
|
||||||
|
last_edited: Local::now(),
|
||||||
|
cards: FnvHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn add_card(&mut self, card: Card) {
|
||||||
|
self.cards.insert(card.uuid, card);
|
||||||
|
}
|
||||||
|
pub fn remove_card(&mut self, card_uuid: Uuid) {
|
||||||
|
self.cards.remove(&card_uuid);
|
||||||
|
}
|
||||||
|
pub fn card_exists(&self, card_uuid: Uuid) -> bool {
|
||||||
|
self.cards.contains_key(&card_uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Card {
|
||||||
|
pub fn new() -> Card {
|
||||||
|
Card {
|
||||||
|
uuid: Uuid::new_v4(),
|
||||||
|
title: String::new(),
|
||||||
|
firstname: String::new(),
|
||||||
|
lastname: String::new(),
|
||||||
|
additionalname: String::new(),
|
||||||
|
name_prefix: String::new(),
|
||||||
|
name_suffix: String::new(),
|
||||||
|
//address
|
||||||
|
|
||||||
|
birthday: None,
|
||||||
|
email: String::new(),
|
||||||
|
url: String::new(),
|
||||||
|
key: String::new(),
|
||||||
|
|
||||||
|
last_edited: Local::now(),
|
||||||
|
extra_properties: FnvHashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
self.title.as_str()
|
||||||
|
}
|
||||||
|
pub fn firstname(&self) -> &str {
|
||||||
|
self.firstname.as_str()
|
||||||
|
}
|
||||||
|
pub fn lastname(&self) -> &str {
|
||||||
|
self.lastname.as_str()
|
||||||
|
}
|
||||||
|
pub fn additionalname(&self) -> &str {
|
||||||
|
self.additionalname.as_str()
|
||||||
|
}
|
||||||
|
pub fn name_prefix(&self) -> &str {
|
||||||
|
self.name_prefix.as_str()
|
||||||
|
}
|
||||||
|
pub fn name_suffix(&self) -> &str {
|
||||||
|
self.name_suffix.as_str()
|
||||||
|
}
|
||||||
|
pub fn email(&self) -> &str {
|
||||||
|
self.email.as_str()
|
||||||
|
}
|
||||||
|
pub fn url(&self) -> &str {
|
||||||
|
self.url.as_str()
|
||||||
|
}
|
||||||
|
pub fn key(&self) -> &str {
|
||||||
|
self.key.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&mut self, new: &str) {
|
||||||
|
self.title = new.to_string();()
|
||||||
|
}
|
||||||
|
pub fn set_firstname(&mut self, new: &str) {
|
||||||
|
self.firstname = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_lastname(&mut self, new: &str) {
|
||||||
|
self.lastname = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_additionalname(&mut self, new: &str) {
|
||||||
|
self.additionalname = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_name_prefix(&mut self, new: &str) {
|
||||||
|
self.name_prefix = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_name_suffix(&mut self, new: &str) {
|
||||||
|
self.name_suffix = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_email(&mut self, new: &str) {
|
||||||
|
self.email = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_url(&mut self, new: &str) {
|
||||||
|
self.url = new.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_key(&mut self, new: &str) {
|
||||||
|
self.key = new.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_extra_property(&mut self, key: &str, value: String) {
|
||||||
|
self.extra_properties.insert(key.to_string(), value);
|
||||||
|
}
|
||||||
|
pub fn extra_property(&self, key: &str) -> Option<&str> {
|
||||||
|
self.extra_properties.get(key).map(|v| v.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ pub mod async;
|
||||||
pub mod conf;
|
pub mod conf;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod mailbox;
|
pub mod mailbox;
|
||||||
|
pub mod addressbook;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
@ -37,6 +38,8 @@ extern crate chan;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
|
extern crate uuid;
|
||||||
|
extern crate fnv;
|
||||||
|
|
||||||
pub use conf::*;
|
pub use conf::*;
|
||||||
pub use mailbox::*;
|
pub use mailbox::*;
|
||||||
|
@ -44,3 +47,5 @@ pub use mailbox::*;
|
||||||
pub use error::{MeliError, Result};
|
pub use error::{MeliError, Result};
|
||||||
pub use mailbox::backends::{Backends, RefreshEvent, RefreshEventConsumer};
|
pub use mailbox::backends::{Backends, RefreshEvent, RefreshEventConsumer};
|
||||||
pub use mailbox::email::{Envelope, Flag};
|
pub use mailbox::email::{Envelope, Flag};
|
||||||
|
|
||||||
|
pub use addressbook::*;
|
||||||
|
|
|
@ -67,6 +67,22 @@ pub enum Address {
|
||||||
Group(GroupAddress),
|
Group(GroupAddress),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Address {
|
||||||
|
pub fn get_display_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Address::Mailbox(m) => m.display_name.display(&m.raw),
|
||||||
|
Address::Group(g) => g.display_name.display(&g.raw),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_email(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Address::Mailbox(m) => m.address_spec.display(&m.raw),
|
||||||
|
Address::Group(_) => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Eq for Address {}
|
impl Eq for Address {}
|
||||||
impl PartialEq for Address {
|
impl PartialEq for Address {
|
||||||
fn eq(&self, other: &Address) -> bool {
|
fn eq(&self, other: &Address) -> bool {
|
||||||
|
|
|
@ -66,7 +66,7 @@ fn main() {
|
||||||
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
||||||
let listing = listing::Listing::default();
|
let listing = listing::Listing::default();
|
||||||
let b = Entity::from(Box::new(listing));
|
let b = Entity::from(Box::new(listing));
|
||||||
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
|
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context))]));
|
||||||
let window = Entity::from(tabs);
|
let window = Entity::from(tabs);
|
||||||
|
|
||||||
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
|
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
|
||||||
|
|
|
@ -19,4 +19,5 @@ nom = "3.2.0"
|
||||||
notify = "4.0.1"
|
notify = "4.0.1"
|
||||||
notify-rust = "^3"
|
notify-rust = "^3"
|
||||||
termion = "1.5.1"
|
termion = "1.5.1"
|
||||||
|
bincode = "1.0.1"
|
||||||
uuid = { version = "0.6", features = ["serde", "v4"] }
|
uuid = { version = "0.6", features = ["serde", "v4"] }
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* meli - contacts module
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use melib::{AddressBook, Card};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ContactManager {
|
||||||
|
content: CellBuffer,
|
||||||
|
dirty: bool,
|
||||||
|
initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ContactManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
ContactManager {
|
||||||
|
content: CellBuffer::default(),
|
||||||
|
dirty: true,
|
||||||
|
initialized: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContactManager {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "contacts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContactManager {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ContactManager {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if !self.initialized {
|
||||||
|
clear_area(grid, area);
|
||||||
|
self.initialized = true;
|
||||||
|
}
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
self.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kill(&mut self, uuid: Uuid) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* meli - ui crate.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ContactsPanel {
|
||||||
|
content: CellBuffer,
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ContactsPanel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "contacts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Component for ContactsPanel {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if self.dirty {
|
||||||
|
self.dirty = false;
|
||||||
|
}
|
||||||
|
clear_area(grid, area);
|
||||||
|
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1)));
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContactsPanel {
|
||||||
|
pub fn new(context: &Context) -> ContactsPanel {
|
||||||
|
let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default());
|
||||||
|
write_string_to_grid(
|
||||||
|
"Contacts",
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((2, 3), (120 - 1, 3)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, a) in context.accounts.iter().enumerate() {
|
||||||
|
create_box(&mut content, ((2,5+i*10 ), (120-1, 15+i*10)));
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
a.name(),
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((3, 5 + i*10), (120 - 2, 5 + i*10)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
" ▒██▒ ",
|
||||||
|
&mut content,
|
||||||
|
Color::Byte(32),
|
||||||
|
Color::Default,
|
||||||
|
((x, y), (120 - 2, 5 + i*10)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
&a.runtime_settings.account().identity,
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((4, y + 2), (120 - 2, y + 2)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactsPanel {
|
||||||
|
content,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* meli - accounts module.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
mod contacts;
|
||||||
|
|
||||||
|
pub use contacts::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AccountsPanel {
|
||||||
|
cursor: usize,
|
||||||
|
content: CellBuffer,
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AccountsPanel {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "accounts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Component for AccountsPanel {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if self.dirty {
|
||||||
|
write_string_to_grid(
|
||||||
|
"Accounts",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((2, 3), (120 - 1, 3)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, a) in context.accounts.iter().enumerate() {
|
||||||
|
create_box(&mut self.content, ((2,5+i*10 ), (120-1, 15+i*10)));
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
a.name(),
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((3, 5 + i*10), (120 - 2, 5 + i*10)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
" ▒██▒ ",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Byte(32),
|
||||||
|
Color::Default,
|
||||||
|
((x, y), (120 - 2, 5 + i*10)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
&a.runtime_settings.account().identity,
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((4, y + 2), (120 - 2, y + 2)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if i == self.cursor {
|
||||||
|
for h in 1..8 {
|
||||||
|
self.content[(2, h+y+1)].set_ch('*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_string_to_grid(
|
||||||
|
"- Settings",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((5, y + 3), (120 - 2, y + 3)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
"- Contacts",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((5, y + 4), (120 - 2, y + 4)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
"- Mailing Lists",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((5, y + 5), (120 - 2, y + 5)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
}
|
||||||
|
clear_area(grid, area);
|
||||||
|
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1)));
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Up) => {
|
||||||
|
self.cursor = self.cursor.saturating_sub(1);
|
||||||
|
self.dirty = true;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Down) => {
|
||||||
|
if self.cursor + 1 < context.accounts.len() {
|
||||||
|
self.cursor += 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountsPanel {
|
||||||
|
pub fn new(context: &Context) -> AccountsPanel {
|
||||||
|
let mut content = CellBuffer::new(120, 25 + context.accounts.len() * 20, Cell::default());
|
||||||
|
|
||||||
|
AccountsPanel {
|
||||||
|
cursor: 0,
|
||||||
|
content,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,9 @@ pub use view::*;
|
||||||
mod compose;
|
mod compose;
|
||||||
pub use self::compose::*;
|
pub use self::compose::*;
|
||||||
|
|
||||||
|
mod accounts;
|
||||||
|
pub use self::accounts::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct AccountMenuEntry {
|
struct AccountMenuEntry {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use linkify::{Link, LinkFinder};
|
use linkify::{Link, LinkFinder};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
use std::any::Any;
|
||||||
|
|
||||||
mod html;
|
mod html;
|
||||||
pub use self::html::*;
|
pub use self::html::*;
|
||||||
|
@ -40,6 +41,7 @@ enum ViewMode {
|
||||||
Attachment(usize),
|
Attachment(usize),
|
||||||
Raw,
|
Raw,
|
||||||
Subview,
|
Subview,
|
||||||
|
ContactSelector(Selector),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ViewMode {
|
impl Default for ViewMode {
|
||||||
|
@ -170,7 +172,8 @@ impl MailView {
|
||||||
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
|
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
|
||||||
ret.push_str(&attachments[aidx].text());
|
ret.push_str(&attachments[aidx].text());
|
||||||
ret
|
ret
|
||||||
}
|
},
|
||||||
|
ViewMode::ContactSelector(_) => { unimplemented!()},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
|
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
|
||||||
|
@ -339,11 +342,15 @@ impl Component for MailView {
|
||||||
};
|
};
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::Subview => {
|
ViewMode::Subview => {
|
||||||
if let Some(s) = self.subview.as_mut() {
|
if let Some(s) = self.subview.as_mut() {
|
||||||
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
ViewMode::ContactSelector(ref mut s) => {
|
||||||
|
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(p) = self.pager.as_mut() {
|
if let Some(p) = self.pager.as_mut() {
|
||||||
|
@ -361,7 +368,12 @@ impl Component for MailView {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
ViewMode::ContactSelector(ref mut s) => {
|
||||||
|
if s.process_event(event, context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(p) = self.pager.as_mut() {
|
if let Some(p) = self.pager.as_mut() {
|
||||||
if p.process_event(event, context) {
|
if p.process_event(event, context) {
|
||||||
|
@ -372,6 +384,36 @@ impl Component for MailView {
|
||||||
}
|
}
|
||||||
|
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Char('c')) => {
|
||||||
|
/*
|
||||||
|
let mut new_card: Card = Card::new();
|
||||||
|
new_card.set_email(&envelope.from()[0].get_email());
|
||||||
|
new_card.set_firstname(&envelope.from()[0].get_display_name());
|
||||||
|
|
||||||
|
eprintln!("{:?}", new_card);
|
||||||
|
|
||||||
|
*/
|
||||||
|
match self.mode {
|
||||||
|
ViewMode::ContactSelector(_) => {
|
||||||
|
if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
|
||||||
|
//eprintln!("{:?}", s.collect());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
let accounts = &context.accounts;
|
||||||
|
let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
entries.push((envelope.from()[0].get_email().into_bytes(), format!("{}", envelope.from()[0])));
|
||||||
|
entries.push((String::from("foo@bar.de").into_bytes(), String::from("Johann de Vir <foo@bar.de>")));
|
||||||
|
self.mode = ViewMode::ContactSelector(Selector::new(entries, true));
|
||||||
|
//context.accounts.context(self.coordinates.0).address_book.add_card(new_card);
|
||||||
|
},
|
||||||
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
|
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
|
||||||
self.cmd_buf.clear();
|
self.cmd_buf.clear();
|
||||||
context.replies.push_back(UIEvent {
|
context.replies.push_back(UIEvent {
|
||||||
|
@ -556,7 +598,7 @@ impl Component for MailView {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty || true
|
||||||
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,9 @@ pub use self::indexer::*;
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub use self::utilities::*;
|
pub use self::utilities::*;
|
||||||
|
|
||||||
|
pub mod contacts;
|
||||||
|
pub use contacts::*;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
@ -67,6 +70,11 @@ const LIGHT_DOWN_AND_HORIZONTAL: char = '┬';
|
||||||
|
|
||||||
const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
||||||
|
|
||||||
|
const DOUBLE_DOWN_AND_RIGHT: char = '╔';
|
||||||
|
const DOUBLE_DOWN_AND_LEFT: char = '╗';
|
||||||
|
const DOUBLE_UP_AND_LEFT: char = '╝';
|
||||||
|
const DOUBLE_UP_AND_RIGHT: char = '╚';
|
||||||
|
|
||||||
/// `Entity` is a container for Components.
|
/// `Entity` is a container for Components.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Entity {
|
pub struct Entity {
|
||||||
|
@ -405,3 +413,22 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) {
|
||||||
|
|
||||||
grid[idx].set_ch(bin_to_ch(bin_set));
|
grid[idx].set_ch(bin_to_ch(bin_set));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_box(grid: &mut CellBuffer, area: Area) {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
|
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||||
|
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
|
||||||
|
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
|
||||||
|
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
|
||||||
|
}
|
||||||
|
set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
|
||||||
|
set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
|
||||||
|
}
|
||||||
|
|
|
@ -836,3 +836,114 @@ impl Component for Tabbed {
|
||||||
self.children[self.cursor_pos].set_dirty();
|
self.children[self.cursor_pos].set_dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type EntryIdentifier = Vec<u8>;
|
||||||
|
/// Shows selection to user
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Selector {
|
||||||
|
single_only: bool, /// allow only one selection
|
||||||
|
entries: Vec<(EntryIdentifier, bool)>,
|
||||||
|
selected_entry_count: u32,
|
||||||
|
content: CellBuffer,
|
||||||
|
|
||||||
|
cursor: usize,
|
||||||
|
|
||||||
|
dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Selector {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
Display::fmt("Selector", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Selector {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
copy_area_with_break(
|
||||||
|
grid,
|
||||||
|
&self.content,
|
||||||
|
area,
|
||||||
|
((0, 0), (width, height)),
|
||||||
|
);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Char(' ')) => {
|
||||||
|
self.entries[self.cursor].1 = ! self.entries[self.cursor].1;
|
||||||
|
if self.entries[self.cursor].1 {
|
||||||
|
write_string_to_grid(
|
||||||
|
"x",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((1, self.cursor), (width, self.cursor)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
write_string_to_grid(
|
||||||
|
" ",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((1, self.cursor), (width, self.cursor)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Up) if self.cursor > 0 => {
|
||||||
|
self.cursor -= 1;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Down) if self.cursor < height - 1=> {
|
||||||
|
self.cursor += 1;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selector {
|
||||||
|
pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> Selector {
|
||||||
|
let width = entries.iter().max_by_key(|e| e.1.len()).map(|v| v.1.len()).unwrap_or(0) + 4;
|
||||||
|
let height = entries.len();
|
||||||
|
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
|
||||||
|
let identifiers = entries.iter_mut().map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false)).collect();
|
||||||
|
for (i, e) in entries.into_iter().enumerate() {
|
||||||
|
write_string_to_grid(
|
||||||
|
&format!("[ ] {}", e.1),
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((0, i), (width - 1, i)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Selector {
|
||||||
|
single_only,
|
||||||
|
entries: identifiers,
|
||||||
|
selected_entry_count: 0,
|
||||||
|
content,
|
||||||
|
cursor: 0,
|
||||||
|
dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect(self) -> Vec<EntryIdentifier> {
|
||||||
|
self.entries.into_iter().filter(|v| v.1).map(|(id, _)| id).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@ use mailbox::backends::{
|
||||||
};
|
};
|
||||||
use mailbox::*;
|
use mailbox::*;
|
||||||
use melib::error::Result;
|
use melib::error::Result;
|
||||||
|
use melib::AddressBook;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::result;
|
use std::result;
|
||||||
|
@ -48,17 +52,38 @@ macro_rules! mailbox {
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
name: String,
|
name: String,
|
||||||
folders: Vec<Option<Result<Mailbox>>>,
|
folders: Vec<Option<Result<Mailbox>>>,
|
||||||
|
|
||||||
pub workers: Vec<Worker>,
|
|
||||||
|
|
||||||
sent_folder: Option<usize>,
|
sent_folder: Option<usize>,
|
||||||
|
|
||||||
pub settings: AccountConf,
|
pub(crate) address_book: AddressBook,
|
||||||
pub runtime_settings: AccountConf,
|
|
||||||
pub backend: Box<MailBackend>,
|
pub(crate) workers: Vec<Worker>,
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) settings: AccountConf,
|
||||||
|
pub(crate) runtime_settings: AccountConf,
|
||||||
|
pub(crate) backend: Box<MailBackend>,
|
||||||
notify_fn: Arc<NotifyFn>,
|
notify_fn: Arc<NotifyFn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Account {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let data_dir =
|
||||||
|
xdg::BaseDirectories::with_profile("meli", &self.name)
|
||||||
|
.unwrap();
|
||||||
|
if let Ok(data) = data_dir.place_data_file("addressbook") {
|
||||||
|
/* place result in cache directory */
|
||||||
|
let f = match fs::File::create(data) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("{}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let writer = io::BufWriter::new(f);
|
||||||
|
bincode::serialize_into(writer, &self.address_book).unwrap();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
pub fn new(name: String, settings: AccountConf, map: &Backends, notify_fn: NotifyFn) -> Self {
|
pub fn new(name: String, settings: AccountConf, map: &Backends, notify_fn: NotifyFn) -> Self {
|
||||||
let mut backend = map.get(settings.account().format())(settings.account());
|
let mut backend = map.get(settings.account().format())(settings.account());
|
||||||
|
@ -73,12 +98,31 @@ impl Account {
|
||||||
folders.push(None);
|
folders.push(None);
|
||||||
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
|
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
|
||||||
}
|
}
|
||||||
eprintln!("sent_folder for {} is {:?}", name, sent_folder);
|
let data_dir =
|
||||||
|
xdg::BaseDirectories::with_profile("meli", &name)
|
||||||
|
.unwrap();
|
||||||
|
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") {
|
||||||
|
if data.exists() {
|
||||||
|
let reader = io::BufReader::new(fs::File::open(data).unwrap());
|
||||||
|
let result: result::Result<AddressBook, _> = bincode::deserialize_from(reader);
|
||||||
|
if let Ok(mut data_t) = result {
|
||||||
|
data_t
|
||||||
|
} else {
|
||||||
|
AddressBook::new(name.clone())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AddressBook::new(name.clone())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AddressBook::new(name.clone())
|
||||||
|
};
|
||||||
|
|
||||||
Account {
|
Account {
|
||||||
name,
|
name,
|
||||||
folders,
|
folders,
|
||||||
workers,
|
address_book,
|
||||||
sent_folder,
|
sent_folder,
|
||||||
|
workers,
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
runtime_settings: settings,
|
runtime_settings: settings,
|
||||||
backend,
|
backend,
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
extern crate config;
|
extern crate config;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate xdg;
|
extern crate xdg;
|
||||||
|
extern crate bincode;
|
||||||
|
|
||||||
pub mod pager;
|
pub mod pager;
|
||||||
|
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
|
|
Loading…
Reference in New Issue