web: style search toolbar

This commit is contained in:
Bill Thiede 2025-01-26 12:24:06 -08:00
parent b2879211e4
commit 89897aa48f

View File

@ -20,37 +20,41 @@ use crate::{
// format!() calls all over with magic strings representing notmuch specific syntax.
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
const BUTTON_CLASSES: &[&str] = &[
"bg-slate-900",
"rounded-md",
"p-2",
"border",
"border-slate-700",
"text-center",
"text-sm",
"transition-all",
"shadow-md",
"hover:shadow-lg",
"hover:bg-slate-700",
"disabled:pointer-events-none",
"disabled:opacity-50",
"disabled:shadow-none",
];
pub fn view(model: &Model) -> Node<Msg> {
let show_icon_text = true;
let content = match &model.context {
Context::None => div![h1!["Loading"]],
Context::ThreadResult {
thread: ShowThreadQueryThread::EmailThread(thread_data),
open_messages,
} => thread(
thread_data,
open_messages,
show_icon_text,
&model.content_el,
),
} => thread(thread_data, open_messages, &model.content_el),
Context::ThreadResult {
thread: ShowThreadQueryThread::NewsPost(post),
..
} => news_post(post, show_icon_text, &model.content_el),
} => news_post(post, &model.content_el),
Context::SearchResult {
query,
results,
count,
pager,
selected_threads,
} => search_results(
&query,
results.as_slice(),
*count,
pager,
selected_threads,
show_icon_text,
),
} => search_results(&query, results.as_slice(), *count, pager, selected_threads),
};
div![
C!["flex", "flex-wrap-reverse", "bg-black", "text-white"],
@ -61,10 +65,10 @@ pub fn view(model: &Model) -> Node<Msg> {
],
reading_progress(model.read_completion_ratio),
div![
C!["flex-auto"],
view_header(&model.query, &model.refreshing_state),
C!["flex-auto", "p-4"],
view_header(&model.query, &model.refreshing_state, true),
content,
view_header(&model.query, &model.refreshing_state),
view_header(&model.query, &model.refreshing_state, false),
]
]
}
@ -75,7 +79,6 @@ fn search_results(
count: usize,
pager: &FrontPageQuerySearchPageInfo,
selected_threads: &HashSet<String>,
show_icon_text: bool,
) -> Node<Msg> {
if query.is_empty() {
set_title("all mail");
@ -140,12 +143,13 @@ fn search_results(
});
let show_bulk_edit = !selected_threads.is_empty();
div![
C!["NOTPORTED", "search-results"],
search_toolbar(count, pager, show_bulk_edit, show_icon_text),
div![C!["NOTPORTED", "index"], rows],
search_toolbar(count, pager, show_bulk_edit, show_icon_text),
C!["flex", "flex-col"],
search_toolbar(count, pager, show_bulk_edit),
div![rows],
search_toolbar(count, pager, show_bulk_edit),
]
}
fn set_title(title: &str) {
seed::document().set_title(&format!("lb: {}", title));
}
@ -275,7 +279,6 @@ fn view_search_results(
count: usize,
pager: &FrontPageQuerySearchPageInfo,
selected_threads: &HashSet<String>,
show_icon_text: bool,
) -> Node<Msg> {
if query.is_empty() {
set_title("all mail");
@ -363,7 +366,7 @@ fn view_search_results(
div![
C!["NOTPORTED", "search-results"],
search_toolbar(count, pager, show_bulk_edit, show_icon_text),
search_toolbar(count, pager, show_bulk_edit),
table![
C![
"table",
@ -399,7 +402,7 @@ fn view_search_results(
]],
tbody![rows]
],
search_toolbar(count, pager, show_bulk_edit, show_icon_text)
search_toolbar(count, pager, show_bulk_edit)
]
}
@ -407,41 +410,39 @@ fn search_toolbar(
count: usize,
pager: &FrontPageQuerySearchPageInfo,
show_bulk_edit: bool,
show_icon_text: bool,
) -> Node<Msg> {
info!("pager {pager:#?}");
nav![
C!["NOTPORTED", "level", "is-mobile"],
C!["p-4", "flex", "w-full", "justify-between"],
div![
C!["NOTPORTED", "level-left"],
C!["gap-2", "flex"],
IF!(show_bulk_edit =>
div![
C!["NOTPORTED","level-item"],
div![C!["NOTPORTED","buttons", "has-addons"],
div![
button![
C!["NOTPORTED","button", "mark-read"],
C![&BUTTON_CLASSES, "rounded-r-none"],
attrs!{At::Title => "Mark as read"},
span![C!["NOTPORTED","icon", "is-small"], i![C!["NOTPORTED","far", "fa-envelope-open"]]],
IF!(show_icon_text=>span!["Read"]),
span![i![C!["far", "fa-envelope-open"]]],
span![C!["pl-2", "hidden", "md:inline"], "Read"],
ev(Ev::Click, |_| Msg::SelectionMarkAsRead)
],
button![
C!["NOTPORTED","button", "mark-unread"],
C![&BUTTON_CLASSES, "rounded-l-none"],
attrs!{At::Title => "Mark as unread"},
span![C!["NOTPORTED","icon", "is-small"], i![C!["NOTPORTED","far", "fa-envelope"]]],
IF!(show_icon_text=>span!["Unread"]),
span![i![C!["far", "fa-envelope"]]],
span![C!["pl-2", "hidden", "md:inline"], "Unread"],
ev(Ev::Click, |_| Msg::SelectionMarkAsUnread)
]
]
]),
IF!(show_bulk_edit =>
div![
C!["NOTPORTED","level-item"],
div![C!["NOTPORTED","buttons", "has-addons"],
div![
button![
C!["NOTPORTED","button", "spam"],
C![&BUTTON_CLASSES, "text-red-500"],
attrs!{At::Title => "Mark as spam"},
span![C!["NOTPORTED","icon", "is-small"], i![C!["NOTPORTED","far", "fa-hand"]]],
IF!(show_icon_text=>span!["Spam"]),
span![i![C!["far", "fa-hand"]]],
span![C!["pl-2", "hidden", "md:inline"], "Spam"],
ev(Ev::Click, |_|
Msg::MultiMsg(vec![
Msg::SelectionAddTag("Spam".to_string()),
@ -450,36 +451,22 @@ fn search_toolbar(
)
],
],
])
]),
],
div![
C!["NOTPORTED", "level-right"],
nav![
C!["NOTPORTED", "level-item", "pagination"],
a![
C![
"pagination-previous",
"button",
//IF!(!pager.has_previous_page => "is-static"),
],
IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }),
"<",
IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)),
],
a![
C![
"pagination-next",
"button",
//IF!(!pager.has_next_page => "is-static")
],
IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }),
">",
IF!(pager.has_next_page => ev(Ev::Click, |_| Msg::NextPage))
],
ul![
C!["NOTPORTED", "pagination-list"],
li![format!("{count} results")],
],
C!["flex", "gap-2", "items-center"],
p![format!("{count} results")],
button![
C![&BUTTON_CLASSES],
IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }),
"<",
IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)),
],
button![
C![&BUTTON_CLASSES],
IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }),
">",
IF!(pager.has_next_page => ev(Ev::Click, |_| Msg::NextPage))
]
]
]
@ -854,9 +841,10 @@ fn message_render(msg: &ShowThreadQueryThreadOnEmailThreadMessages, open: bool)
fn thread(
thread: &ShowThreadQueryThreadOnEmailThread,
open_messages: &HashSet<String>,
show_icon_text: bool,
content_el: &ElRef<HtmlElement>,
) -> Node<Msg> {
// TODO remove and replace with CSS styling
let show_icon_text = true;
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject
let subject = if thread.subject.is_empty() {
"(no subject)"
@ -977,7 +965,11 @@ fn view_content_tree(content_tree: &str) -> Node<Msg> {
]
}
fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
fn view_header(
query: &str,
refresh_request: &RefreshingState,
auto_focus_search: bool,
) -> Node<Msg> {
let is_loading = refresh_request == &RefreshingState::Loading;
let is_error = if let RefreshingState::Error(err) = refresh_request {
error!("Failed to refresh: {err:?}");
@ -987,54 +979,47 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
};
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
nav![
C!["NOTPORTED", "navbar"],
attrs! {At::Role=>"navigation"},
div![
C!["NOTPORTED", "navbar-start"],
a![
C![
"NOTPORTED",
"navbar-item",
"button",
IF![is_error => "is-danger"]
],
span![i![C![
"fa-solid",
"fa-arrow-rotate-right",
"refresh",
IF![is_loading => "loading"],
]]],
ev(Ev::Click, |_| Msg::RefreshStart),
],
a![
C!["NOTPORTED", "navbar-item", "button"],
attrs! {
At::Href => urls::search(unread_query(), 0)
},
"Unread",
],
a![
C!["NOTPORTED", "navbar-item", "button"],
attrs! {
At::Href => urls::search("", 0)
},
"All",
],
input![
C!["NOTPORTED", "navbar-item", "input"],
attrs! {
At::Placeholder => "Search";
At::AutoFocus => true.as_at_value();
At::Value => query,
},
input_ev(Ev::Input, |q| Msg::UpdateQuery(q)),
// Send search on enter.
keyboard_ev(Ev::KeyUp, move |e| if e.key_code() == 0x0d {
Msg::SearchQuery(query)
} else {
Msg::Noop
}),
]
C!["flex"],
a![
C![IF![is_error => "bg-red-500"], "rounded-r-none"],
C![&BUTTON_CLASSES],
span![i![C![
"fa-solid",
"fa-arrow-rotate-right",
IF![is_loading => "animate-spin"],
]]],
ev(Ev::Click, |_| Msg::RefreshStart),
],
a![
C![&BUTTON_CLASSES],
C!["px-4", "rounded-none"],
attrs! {
At::Href => urls::search(unread_query(), 0)
},
"Unread",
],
a![
C![&BUTTON_CLASSES],
C!["px-4", "rounded-none"],
attrs! {
At::Href => urls::search("", 0)
},
"All",
],
input![
C!["grow", "px-4", "text-black"],
attrs! {
At::Placeholder => "Search";
At::AutoFocus => auto_focus_search.as_at_value();
At::Value => query,
},
input_ev(Ev::Input, |q| Msg::UpdateQuery(q)),
// Send search on enter.
keyboard_ev(Ev::KeyUp, move |e| if e.key_code() == 0x0d {
Msg::SearchQuery(query)
} else {
Msg::Noop
}),
]
]
}
@ -1153,11 +1138,9 @@ pub fn tags(model: &Model) -> Node<Msg> {
]
]
}
fn news_post(
post: &ShowThreadQueryThreadOnNewsPost,
show_icon_text: bool,
content_el: &ElRef<HtmlElement>,
) -> Node<Msg> {
fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef<HtmlElement>) -> Node<Msg> {
// TODO remove and replace with CSS styling
let show_icon_text = true;
let subject = &post.title;
set_title(subject);
let read_thread_id = post.thread_id.clone();