From 25d31a6ce7fbf7447d88de1d704cee3c530c5d0c Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 26 Jan 2025 09:31:44 -0800 Subject: [PATCH] web: only use one view function, desktop/tablet/mobile handled in CSS --- web/src/state.rs | 2 + web/src/view/desktop.rs | 49 ------------- web/src/view/mobile.rs | 127 --------------------------------- web/src/view/mod.rs | 154 +++++++++++++++++++++++++++++++++------- web/src/view/tablet.rs | 48 ------------- 5 files changed, 131 insertions(+), 249 deletions(-) delete mode 100644 web/src/view/desktop.rs delete mode 100644 web/src/view/mobile.rs delete mode 100644 web/src/view/tablet.rs diff --git a/web/src/state.rs b/web/src/state.rs index 07f1b08..cd147b5 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -507,6 +507,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { }); } Msg::WindowScrolled => { + info!("WindowScrolled"); if let Some(el) = model.content_el.get() { let ih = window() .inner_height() @@ -515,6 +516,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { .value_of(); let r = el.get_bounding_client_rect(); + info!("r {r:?} ih {ih}"); if r.height() < ih { // The whole content fits in the window, no scrollbar orders.send_msg(Msg::SetProgress(0.)); diff --git a/web/src/view/desktop.rs b/web/src/view/desktop.rs deleted file mode 100644 index cb53d82..0000000 --- a/web/src/view/desktop.rs +++ /dev/null @@ -1,49 +0,0 @@ -use seed::{prelude::*, *}; -use seed_hooks::topo; - -use crate::{ - graphql::show_thread_query::*, - state::{Context, Model, Msg}, - view::{self, reading_progress, view_header, view_search_results}, -}; - -#[topo::nested] -pub(super) fn view(model: &Model) -> Node { - let show_icon_text = true; - // Do two queries, one without `unread` so it loads fast, then a second with unread. - let content = match &model.context { - Context::None => div![h1!["Loading"]], - Context::ThreadResult { - thread: ShowThreadQueryThread::EmailThread(thread), - open_messages, - } => view::thread(thread, open_messages, show_icon_text, &model.content_el), - Context::ThreadResult { - thread: ShowThreadQueryThread::NewsPost(post), - .. - } => view::news_post(post, show_icon_text, &model.content_el), - Context::SearchResult { - query, - results, - count, - pager, - selected_threads, - } => view_search_results( - &query, - results.as_slice(), - *count, - pager, - selected_threads, - show_icon_text, - ), - }; - div![ - C!["flex"], - reading_progress(model.read_completion_ratio), - div![view::tags(model), view::versions(&model.versions)], - div![ - view_header(&model.query, &model.refreshing_state), - content, - view_header(&model.query, &model.refreshing_state), - ] - ] -} diff --git a/web/src/view/mobile.rs b/web/src/view/mobile.rs deleted file mode 100644 index 34c0c3e..0000000 --- a/web/src/view/mobile.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::collections::HashSet; - -use seed::{prelude::*, *}; - -use crate::{ - api::urls, - graphql::{front_page_query::*, show_thread_query::*}, - state::{Context, Model, Msg}, - view::{ - self, human_age, pretty_authors, reading_progress, search_toolbar, set_title, tags_chiclet, - view_header, - }, -}; - -pub(super) fn view(model: &Model) -> Node { - let show_icon_text = false; - let content = match &model.context { - Context::None => div![h1!["Loading"]], - Context::ThreadResult { - thread: ShowThreadQueryThread::EmailThread(thread), - open_messages, - } => view::thread(thread, open_messages, show_icon_text, &model.content_el), - Context::ThreadResult { - thread: ShowThreadQueryThread::NewsPost(post), - .. - } => view::news_post(post, show_icon_text, &model.content_el), - Context::SearchResult { - query, - results, - count, - pager, - selected_threads, - } => search_results( - &query, - results.as_slice(), - *count, - pager, - selected_threads, - show_icon_text, - ), - }; - div![ - reading_progress(model.read_completion_ratio), - view_header(&model.query, &model.refreshing_state), - content, - view_header(&model.query, &model.refreshing_state), - div![view::tags(model), view::versions(&model.versions)] - ] -} - -fn search_results( - query: &str, - results: &[FrontPageQuerySearchNodes], - count: usize, - pager: &FrontPageQuerySearchPageInfo, - selected_threads: &HashSet, - show_icon_text: bool, -) -> Node { - if query.is_empty() { - set_title("all mail"); - } else { - set_title(query); - } - let rows = results.iter().map(|r| { - let tid = r.thread.clone(); - let check_tid = r.thread.clone(); - let datetime = human_age(r.timestamp as i64); - let unread_idx = r.tags.iter().position(|e| e == &"unread"); - let mut tags = r.tags.clone(); - if let Some(idx) = unread_idx { - tags.remove(idx); - }; - div![ - C!["NOTPORTED", "row"], - label![ - C!["NOTPORTED", "b-checkbox", "checkbox", "is-large"], - input![attrs! { - At::Type=>"checkbox", - At::Checked=>selected_threads.contains(&tid).as_at_value(), - }], - span![C!["NOTPORTED", "check"]], - ev(Ev::Input, move |e| { - if let Some(input) = e - .target() - .as_ref() - .expect("failed to get reference to target") - .dyn_ref::() - { - if input.checked() { - Msg::SelectionAddThread(check_tid) - } else { - Msg::SelectionRemoveThread(check_tid) - } - } else { - Msg::Noop - } - }), - ], - a![ - C!["NOTPORTED", "has-text-light", "summary"], - IF!(unread_idx.is_some() => C!["NOTPORTED","unread"]), - attrs! { - At::Href => urls::thread(&tid) - }, - div![C!["NOTPORTED", "subject"], &r.subject], - span![ - C!["NOTPORTED", "from", "is-size-7"], - pretty_authors(&r.authors) - ], - div![ - span![C!["NOTPORTED", "is-size-7"], tags_chiclet(&tags, true)], - span![ - C!["NOTPORTED", "is-size-7", "float-right", "date"], - datetime - ] - ] - ] - ] - }); - 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), - ] -} diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index bbe9c41..680a954 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -12,17 +12,140 @@ use web_sys::HtmlElement; use crate::{ api::urls, graphql::{front_page_query::*, show_thread_query::*}, - state::{unread_query, Model, Msg, RefreshingState, Tag}, + state::{unread_query, Context, Model, Msg, RefreshingState, Tag}, }; -mod desktop; -mod mobile; -mod tablet; - // TODO(wathiede): create a QueryString enum that wraps single and multiple message ids and thread // ids, and has a to_query_string() that knows notmuch's syntax. Then remove the smattering of // format!() calls all over with magic strings representing notmuch specific syntax. const MAX_RAW_MESSAGE_SIZE: usize = 100_000; + +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, + ), + Context::ThreadResult { + thread: ShowThreadQueryThread::NewsPost(post), + .. + } => news_post(post, show_icon_text, &model.content_el), + Context::SearchResult { + query, + results, + count, + pager, + selected_threads, + } => search_results( + &query, + results.as_slice(), + *count, + pager, + selected_threads, + show_icon_text, + ), + }; + div![ + C!["flex", "flex-wrap-reverse"], + div![ + C!["w-48", "flex-none", "flex", "flex-col"], + tags(model), + versions(&model.versions) + ], + reading_progress(model.read_completion_ratio), + div![ + C!["flex-auto"], + view_header(&model.query, &model.refreshing_state), + content, + view_header(&model.query, &model.refreshing_state), + ] + ] +} + +fn search_results( + query: &str, + results: &[FrontPageQuerySearchNodes], + count: usize, + pager: &FrontPageQuerySearchPageInfo, + selected_threads: &HashSet, + show_icon_text: bool, +) -> Node { + if query.is_empty() { + set_title("all mail"); + } else { + set_title(query); + } + let rows = results.iter().map(|r| { + let tid = r.thread.clone(); + let check_tid = r.thread.clone(); + let datetime = human_age(r.timestamp as i64); + let unread_idx = r.tags.iter().position(|e| e == &"unread"); + let mut tags = r.tags.clone(); + if let Some(idx) = unread_idx { + tags.remove(idx); + }; + div![ + C!["NOTPORTED", "row"], + label![ + C!["NOTPORTED", "b-checkbox", "checkbox", "is-large"], + input![attrs! { + At::Type=>"checkbox", + At::Checked=>selected_threads.contains(&tid).as_at_value(), + }], + span![C!["NOTPORTED", "check"]], + ev(Ev::Input, move |e| { + if let Some(input) = e + .target() + .as_ref() + .expect("failed to get reference to target") + .dyn_ref::() + { + if input.checked() { + Msg::SelectionAddThread(check_tid) + } else { + Msg::SelectionRemoveThread(check_tid) + } + } else { + Msg::Noop + } + }), + ], + a![ + C!["NOTPORTED", "has-text-light", "summary"], + IF!(unread_idx.is_some() => C!["NOTPORTED","unread"]), + attrs! { + At::Href => urls::thread(&tid) + }, + div![C!["NOTPORTED", "subject"], &r.subject], + span![ + C!["NOTPORTED", "from", "is-size-7"], + pretty_authors(&r.authors) + ], + div![ + span![C!["NOTPORTED", "is-size-7"], tags_chiclet(&tags, true)], + span![ + C!["NOTPORTED", "is-size-7", "float-right", "date"], + datetime + ] + ] + ] + ] + }); + 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), + ] +} fn set_title(title: &str) { seed::document().set_title(&format!("lb: {}", title)); } @@ -916,25 +1039,6 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node { ] } -// `view` describes what to display. -pub fn view(model: &Model) -> Node { - let win = seed::window(); - let w = win - .inner_width() - .expect("window width") - .as_f64() - .expect("window width f64"); - let _h = win - .inner_height() - .expect("window height") - .as_f64() - .expect("window height f64"); - div![match w { - w if w < 800. => div![C!["NOTPORTED", "mobile"], mobile::view(model)], - w if w < 1024. => div![C!["NOTPORTED", "tablet"], tablet::view(model)], - _ => div![C!["NOTPORTED", "desktop"], desktop::view(model)], - },] -} pub fn tags(model: &Model) -> Node { fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node { let href = if search_unread { @@ -977,7 +1081,7 @@ pub fn tags(model: &Model) -> Node { for t in tags { let parts: Vec<_> = t.name.split('/').collect(); let mut n = matches(&last, &parts); - if n <= parts.len() - 2 && parts.len() > 1 { + if n + 2 <= parts.len() && parts.len() > 1 { // Synthesize fake tags for proper indenting. for i in n..parts.len() - 1 { let display_name = parts[n]; diff --git a/web/src/view/tablet.rs b/web/src/view/tablet.rs deleted file mode 100644 index 2a37312..0000000 --- a/web/src/view/tablet.rs +++ /dev/null @@ -1,48 +0,0 @@ -use seed::{prelude::*, *}; - -use crate::{ - graphql::show_thread_query::*, - state::{Context, Model, Msg}, - view::{self, reading_progress, view_header, view_search_results}, -}; - -pub(super) fn view(model: &Model) -> Node { - let show_icon_text = false; - // Do two queries, one without `unread` so it loads fast, then a second with unread. - let content = match &model.context { - Context::None => div![h1!["Loading"]], - Context::ThreadResult { - thread: ShowThreadQueryThread::EmailThread(thread), - open_messages, - } => view::thread(thread, open_messages, show_icon_text, &model.content_el), - Context::ThreadResult { - thread: ShowThreadQueryThread::NewsPost(post), - .. - } => view::news_post(post, show_icon_text, &model.content_el), - Context::SearchResult { - query, - results, - count, - pager, - selected_threads, - } => view_search_results( - &query, - results.as_slice(), - *count, - pager, - selected_threads, - show_icon_text, - ), - }; - div![ - C!["NOTPORTED", "main-content"], - div![ - reading_progress(model.read_completion_ratio), - view_header(&model.query, &model.refreshing_state), - content, - view_header(&model.query, &model.refreshing_state), - view::tags(model), - view::versions(&model.versions) - ] - ] -}