diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index 031fabb..0dfee47 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -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 { + let indeterminate = show_bulk_edit && !all_selected; + let tristate_el: ElRef = 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 { } 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);