web: add de/select all checkbox with tristate

This commit is contained in:
Bill Thiede 2025-02-22 17:22:34 -08:00
parent 73433711ca
commit 0cba3a624c

View File

@ -7,7 +7,7 @@ use letterbox_shared::compute_color;
use log::{debug, error, info};
use seed::{prelude::*, *};
use seed_hooks::{state_access::CloneState, topo, use_state, StateAccessEventHandlers};
use web_sys::HtmlElement;
use web_sys::{HtmlElement, HtmlInputElement};
use crate::{
api::urls,
@ -60,6 +60,7 @@ mod tw_classes {
"accent-green-600",
"appearance-none",
"checked:appearance-auto",
"indeterminate:appearance-auto",
"rounded",
"border",
"border-neutral-500",
@ -135,7 +136,6 @@ fn search_results(
tags.remove(idx);
};
let is_unread = unread_idx.is_some();
// TODO: add check-all button, and tristate indicator
div![
C![
"flex",
@ -189,11 +189,12 @@ fn search_results(
]
});
let show_bulk_edit = !selected_threads.is_empty();
let all_selected = selected_threads.len() == results.len();
div![
C!["flex", "flex-col", "flex-auto", "p-4"],
search_toolbar(count, pager, show_bulk_edit),
search_toolbar(count, pager, show_bulk_edit, all_selected),
div![rows],
search_toolbar(count, pager, show_bulk_edit),
search_toolbar(count, pager, show_bulk_edit, all_selected),
]
}
@ -310,47 +311,71 @@ fn human_age(timestamp: i64) -> String {
datetime
}
#[topo::nested]
fn search_toolbar(
count: usize,
pager: &FrontPageQuerySearchPageInfo,
show_bulk_edit: bool,
all_selected: bool,
) -> Node<Msg> {
let indeterminate = show_bulk_edit && !all_selected;
let tristate_el: ElRef<HtmlInputElement> = use_state(|| Default::default()).get();
let tri = el_ref(&tristate_el);
if let Some(tri) = tri.get() {
info!(
"setting tristate to {indeterminate}, current {}",
tri.indeterminate()
);
tri.set_indeterminate(indeterminate);
}
nav![
C!["p-4", "flex", "w-full", "justify-between"],
C!["py-4", "flex", "w-full", "justify-between"],
div![
C!["gap-2", "flex"],
IF!(show_bulk_edit =>
C!["gap-2", "flex", IF!(!show_bulk_edit => "invisible")],
div![
button![
C![&tw_classes::BUTTON, "rounded-r-none"],
attrs!{At::Title => "Mark as read"},
span![i![C!["far", "fa-envelope-open"]]],
span![C!["pl-2", "hidden", "md:inline"], "Read"],
ev(Ev::Click, |_| Msg::SelectionMarkAsRead)
],
button![
C![&tw_classes::BUTTON, "rounded-l-none"],
attrs!{At::Title => "Mark as unread"},
span![i![C!["far", "fa-envelope"]]],
span![C!["pl-2", "hidden", "md:inline"], "Unread"],
ev(Ev::Click, |_| Msg::SelectionMarkAsUnread)
]
]),
IF!(show_bulk_edit =>
C!["flex", "items-center", "mr-4"],
input![
tri,
C![&tw_classes::CHECKBOX],
attrs! {
At::Type=>"checkbox",
At::Checked=>all_selected,
}
],
ev(Ev::Input, move |_| {
if all_selected {
Msg::SelectionSetNone
} else {
Msg::SelectionSetAll
}
}),
],
div![
button![
C![&tw_classes::BUTTON, "text-red-500"],
attrs!{At::Title => "Mark as 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()),
Msg::SelectionMarkAsRead
])
)
button![
C![&tw_classes::BUTTON, "rounded-r-none"],
attrs! {At::Title => "Mark as read"},
span![i![C!["far", "fa-envelope-open"]]],
span![C!["pl-2", "hidden", "md:inline"], "Read"],
ev(Ev::Click, |_| Msg::SelectionMarkAsRead)
],
button![
C![&tw_classes::BUTTON, "rounded-l-none"],
attrs! {At::Title => "Mark as unread"},
span![i![C!["far", "fa-envelope"]]],
span![C!["pl-2", "hidden", "md:inline"], "Unread"],
ev(Ev::Click, |_| Msg::SelectionMarkAsUnread)
]
]),
],
div![button![
C![&tw_classes::BUTTON, "text-red-500"],
attrs! {At::Title => "Mark as 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()),
Msg::SelectionMarkAsRead
]))
]],
],
div![
C!["flex", "gap-2", "items-center"],
@ -877,6 +902,7 @@ fn view_header(
false
};
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
nav![
C!["flex", "px-4", "pt-4", "overflow-hidden"],
a![
@ -1021,9 +1047,6 @@ pub fn tags(model: &Model) -> Node<Msg> {
} else {
a.name.cmp(&b.name)
};
if a.name.starts_with('@') || b.name.starts_with('@') {
info!("a {} < b {} = {r:?}", a.name, b.name,);
}
return r;
});
let tags_open = use_state(|| false);