mirror of https://github.com/terhechte/postsack
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
8.6 KiB
Rust
263 lines
8.6 KiB
Rust
//! A panel to edit filters
|
|
use eframe::egui::{self, vec2, Color32, Response, Widget};
|
|
use eyre::Report;
|
|
|
|
use ps_core::{
|
|
model::{segmentations, Engine},
|
|
Field, Filter, ValueField,
|
|
};
|
|
|
|
/// Filter values for the UI.
|
|
/// All values are mapped as `Option<bool>`
|
|
/// as we have three states for each of them: yes, no, and all
|
|
/// Say for `is_seen1: Only seen Some(yes), Only not seen Some(no), any None
|
|
#[derive(Default)]
|
|
pub struct FilterState {
|
|
/// Yes: All I've send, No: All I've received, None: Any
|
|
is_send: Option<bool>,
|
|
is_reply: Option<bool>,
|
|
is_seen: Option<bool>,
|
|
subject_contains: Option<String>,
|
|
tags_contains: Option<String>,
|
|
}
|
|
|
|
impl FilterState {
|
|
pub fn new() -> Self {
|
|
FilterState {
|
|
is_send: Some(false),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
fn apply(&self, engine: &mut Engine, error: &mut Option<Report>) {
|
|
// FIXME: In principle this could rather be logic for the `engine`, but I'd like to have a generic engine at some point.
|
|
let mut filters = Vec::new();
|
|
if let Some(val) = self.is_send {
|
|
filters.push(Filter::Is(ValueField::bool(&Field::IsSend, val)));
|
|
}
|
|
if let Some(val) = self.is_seen {
|
|
filters.push(Filter::Is(ValueField::bool(&Field::MetaIsSeen, val)));
|
|
}
|
|
if let Some(val) = self.is_reply {
|
|
filters.push(Filter::Is(ValueField::bool(&Field::IsReply, val)));
|
|
}
|
|
// FIXME: The system currently doesn't allow searching for multiple tags
|
|
// (e.g. (x like tag1 or x like tag2))
|
|
// this would require a `Filter::Expression` that is just added verbatim
|
|
if let Some(n) = &self.tags_contains {
|
|
filters.push(Filter::Like(ValueField::string(
|
|
&Field::MetaTags,
|
|
n.clone(),
|
|
)));
|
|
}
|
|
if let Some(n) = &self.subject_contains {
|
|
filters.push(Filter::Contains(ValueField::string(
|
|
&Field::Subject,
|
|
n.clone(),
|
|
)));
|
|
}
|
|
*error = segmentations::set_filters(engine, &filters).err();
|
|
}
|
|
|
|
fn clear(&mut self) {
|
|
self.is_send = None;
|
|
self.is_reply = None;
|
|
self.is_seen = None;
|
|
self.subject_contains = None;
|
|
self.tags_contains = None;
|
|
}
|
|
}
|
|
|
|
pub struct FilterPanel<'a> {
|
|
engine: &'a mut Engine,
|
|
state: &'a mut FilterState,
|
|
error: &'a mut Option<Report>,
|
|
}
|
|
|
|
impl<'a> FilterPanel<'a> {
|
|
pub fn new(
|
|
engine: &'a mut Engine,
|
|
state: &'a mut FilterState,
|
|
error: &'a mut Option<Report>,
|
|
) -> Self {
|
|
Self {
|
|
engine,
|
|
state,
|
|
error,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Widget for FilterPanel<'a> {
|
|
fn ui(self, ui: &mut egui::Ui) -> Response {
|
|
let Self {
|
|
engine,
|
|
state,
|
|
error,
|
|
} = self;
|
|
egui::Frame::none()
|
|
.margin(vec2(15.0, 10.5))
|
|
.show(ui, |ui| {
|
|
FilterPanel::filter_panel_contents(ui, engine, state);
|
|
ui.add_space(5.0);
|
|
ui.horizontal(|ui| {
|
|
if ui.button("Apply").clicked() {
|
|
state.apply(engine, error);
|
|
}
|
|
ui.add_space(10.0);
|
|
if ui.button("Clear").clicked() {
|
|
state.clear();
|
|
state.apply(engine, error);
|
|
}
|
|
});
|
|
})
|
|
.response
|
|
}
|
|
}
|
|
|
|
impl FilterPanel<'_> {
|
|
fn filter_panel_contents(
|
|
ui: &mut egui::Ui,
|
|
engine: &mut Engine,
|
|
state: &mut FilterState,
|
|
) -> Response {
|
|
egui::ScrollArea::vertical()
|
|
.max_height(330.0)
|
|
.show(ui, |ui| {
|
|
egui::Grid::new("filter_grid")
|
|
.spacing(vec2(15.0, 15.0))
|
|
.show(ui, |ui| {
|
|
// We want to have aligned labels for our input fields
|
|
egui::Grid::new("filter_text_grid")
|
|
.spacing(vec2(5.0, 15.0))
|
|
.show(ui, |ui| {
|
|
if let Some((range, total)) = segmentations::segments_range(engine)
|
|
{
|
|
ui.label("Segment Limit");
|
|
let mut selected = total;
|
|
let response = ui.add(egui::Slider::new(&mut selected, range));
|
|
if response.changed() {
|
|
segmentations::set_segments_range(
|
|
engine,
|
|
Some(0..=selected),
|
|
);
|
|
}
|
|
ui.end_row();
|
|
}
|
|
|
|
input_block(ui, "Subject", &mut state.subject_contains);
|
|
ui.end_row();
|
|
});
|
|
ui.end_row();
|
|
|
|
if engine.format_has_tags() {
|
|
input_tags(
|
|
ui,
|
|
"Labels / Tags",
|
|
&mut state.tags_contains,
|
|
engine.known_tags(),
|
|
);
|
|
ui.end_row();
|
|
}
|
|
|
|
radio_group(
|
|
ui,
|
|
"Inbox",
|
|
&["Only Send Mails", "Only Received Mails", "All"],
|
|
&mut state.is_send,
|
|
);
|
|
ui.end_row();
|
|
|
|
radio_group(
|
|
ui,
|
|
"Only Replies",
|
|
&["Yes", "No", "All"],
|
|
&mut state.is_reply,
|
|
);
|
|
ui.end_row();
|
|
|
|
if engine.format_has_seen() {
|
|
radio_group(ui, "Only Seen", &["Yes", "No", "All"], &mut state.is_seen);
|
|
ui.end_row();
|
|
}
|
|
|
|
ui.end_row();
|
|
})
|
|
})
|
|
.response
|
|
}
|
|
}
|
|
|
|
fn radio_group(ui: &mut egui::Ui, title: &str, names: &[&str; 3], value: &mut Option<bool>) {
|
|
let mut radio_value = match value {
|
|
Some(true) => 0,
|
|
Some(false) => 1,
|
|
None => 2,
|
|
};
|
|
ui.vertical(|ui| {
|
|
ui.add(egui::Label::new(title));
|
|
//ui.end_row();
|
|
ui.indent(&title, |ui| {
|
|
ui.radio_value(&mut radio_value, 0, names[0]);
|
|
ui.radio_value(&mut radio_value, 1, names[1]);
|
|
ui.radio_value(&mut radio_value, 2, names[2]);
|
|
});
|
|
});
|
|
|
|
let output_value = match radio_value {
|
|
0 => Some(true),
|
|
1 => Some(false),
|
|
_ => None,
|
|
};
|
|
|
|
*value = output_value;
|
|
}
|
|
|
|
fn input_block(ui: &mut egui::Ui, title: &str, value: &mut Option<String>) {
|
|
let mut text_value = value.clone().unwrap_or_else(|| "".to_string());
|
|
ui.label(title);
|
|
ui.text_edit_singleline(&mut text_value);
|
|
match text_value.as_str() {
|
|
"" => *value = None,
|
|
_ => *value = Some(text_value),
|
|
}
|
|
}
|
|
|
|
fn input_tags(
|
|
ui: &mut egui::Ui,
|
|
title: &str,
|
|
selection: &mut Option<String>,
|
|
available: &[String],
|
|
) {
|
|
ui.vertical(|ui| {
|
|
ui.add(egui::Label::new(title));
|
|
egui::Frame::none()
|
|
.margin((5.0, 5.0))
|
|
.corner_radius(8.0)
|
|
.fill(Color32::BLACK)
|
|
.show(ui, |ui| {
|
|
ui.horizontal_wrapped(|ui| {
|
|
// Overly complicated, but can later on easily be extended for multi selection
|
|
for (index, tag) in available.iter().enumerate() {
|
|
let was_selected = if let Some(n) = selection {
|
|
n == tag
|
|
} else {
|
|
false
|
|
};
|
|
let mut selected = if was_selected { index } else { 9999 };
|
|
if ui.selectable_value(&mut selected, index, tag).clicked() {
|
|
if was_selected {
|
|
*selection = None;
|
|
} else {
|
|
*selection = Some(tag.clone());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
// if ui.button("Clear").clicked() {
|
|
// selection.clear();
|
|
// }
|
|
});
|
|
}
|