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