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![
"pagination-previous",
"button",
//IF!(!pager.has_previous_page => "is-static"),
],
IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }), IF!(!pager.has_previous_page => attrs!{ At::Disabled=>true }),
"<", "<",
IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)), IF!(pager.has_previous_page => ev(Ev::Click, |_| Msg::PreviousPage)),
], ],
a![ button![
C![ C![&BUTTON_CLASSES],
"pagination-next",
"button",
//IF!(!pager.has_next_page => "is-static")
],
IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }), IF!(!pager.has_next_page => attrs!{ At::Disabled=>true }),
">", ">",
IF!(pager.has_next_page => ev(Ev::Click, |_| Msg::NextPage)) 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,44 +979,38 @@ 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"},
div![
C!["NOTPORTED", "navbar-start"],
a![ a![
C![ C![IF![is_error => "bg-red-500"], "rounded-r-none"],
"NOTPORTED", C![&BUTTON_CLASSES],
"navbar-item",
"button",
IF![is_error => "is-danger"]
],
span![i![C![ span![i![C![
"fa-solid", "fa-solid",
"fa-arrow-rotate-right", "fa-arrow-rotate-right",
"refresh", IF![is_loading => "animate-spin"],
IF![is_loading => "loading"],
]]], ]]],
ev(Ev::Click, |_| Msg::RefreshStart), ev(Ev::Click, |_| Msg::RefreshStart),
], ],
a![ a![
C!["NOTPORTED", "navbar-item", "button"], C![&BUTTON_CLASSES],
C!["px-4", "rounded-none"],
attrs! { attrs! {
At::Href => urls::search(unread_query(), 0) At::Href => urls::search(unread_query(), 0)
}, },
"Unread", "Unread",
], ],
a![ a![
C!["NOTPORTED", "navbar-item", "button"], C![&BUTTON_CLASSES],
C!["px-4", "rounded-none"],
attrs! { attrs! {
At::Href => urls::search("", 0) At::Href => urls::search("", 0)
}, },
"All", "All",
], ],
input![ input![
C!["NOTPORTED", "navbar-item", "input"], C!["grow", "px-4", "text-black"],
attrs! { attrs! {
At::Placeholder => "Search"; At::Placeholder => "Search";
At::AutoFocus => true.as_at_value(); At::AutoFocus => auto_focus_search.as_at_value();
At::Value => query, At::Value => query,
}, },
input_ev(Ev::Input, |q| Msg::UpdateQuery(q)), input_ev(Ev::Input, |q| Msg::UpdateQuery(q)),
@ -1036,7 +1022,6 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
}), }),
] ]
] ]
]
} }
pub fn tags(model: &Model) -> Node<Msg> { pub fn tags(model: &Model) -> Node<Msg> {
@ -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();