diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index d8c226a..65b61d1 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -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 { - 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 { ], 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, - show_icon_text: bool, ) -> Node { 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, - show_icon_text: bool, ) -> Node { 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 { + 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, - show_icon_text: bool, content_el: &ElRef, ) -> Node { + // 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 { ] } -fn view_header(query: &str, refresh_request: &RefreshingState) -> Node { +fn view_header( + query: &str, + refresh_request: &RefreshingState, + auto_focus_search: bool, +) -> Node { 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 { }; 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 { ] ] } -fn news_post( - post: &ShowThreadQueryThreadOnNewsPost, - show_icon_text: bool, - content_el: &ElRef, -) -> Node { +fn news_post(post: &ShowThreadQueryThreadOnNewsPost, content_el: &ElRef) -> Node { + // 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();