From 0b4cfadf88bd120df082933d8fceb8f6560c07d9 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 4 Mar 2023 09:00:18 -0800 Subject: [PATCH] Add refresh button and mobile view. --- Cargo.lock | 1 + server/src/main.rs | 5 ++ web/Cargo.toml | 7 +++ web/src/lib.rs | 112 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 111 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb53dcc..d01bd1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -983,6 +983,7 @@ dependencies = [ "serde_json", "wasm-bindgen-test", "wasm-timer", + "web-sys", ] [[package]] diff --git a/server/src/main.rs b/server/src/main.rs index 9bdbf8b..6a1ca25 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -18,6 +18,10 @@ fn hello() -> &'static str { "Hello, world!" } +#[get("/refresh")] +async fn refresh(nm: &State) -> Result, Debug> { + Ok(Json(String::from_utf8_lossy(&nm.new()?).to_string())) +} #[get("/search")] async fn search_all(nm: &State) -> Result, Debug> { search(nm, "*").await @@ -123,6 +127,7 @@ async fn main() -> Result<(), Box> { original_part, original, hello, + refresh, search_all, search, show_pretty, diff --git a/web/Cargo.toml b/web/Cargo.toml index 1ecd98f..d9b1be9 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -28,3 +28,10 @@ wasm-timer = "0.2.5" [package.metadata.wasm-pack.profile.release] wasm-opt = ['-Os'] + +[dependencies.web-sys] +version = "0.3.58" +features = [ + "MediaQueryList", + "Window" +] diff --git a/web/src/lib.rs b/web/src/lib.rs index 81f2d4f..546930d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -74,6 +74,7 @@ struct Model { // `Msg` describes the different events you can modify state with. enum Msg { Noop, + Refresh, SearchRequest(String), SearchResult(fetch::Result), ShowRequest(String), @@ -86,6 +87,11 @@ enum Msg { fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { match msg { Msg::Noop => {} + Msg::Refresh => { + orders.skip().perform_cmd(async move { + refresh_request().await; + }); + } Msg::SearchRequest(query) => { model.query = query.clone(); @@ -147,20 +153,35 @@ async fn search_request(query: &str) -> fetch::Result { mod api { const BASE_URL: &str = "/api"; + pub fn refresh() -> String { + format!("{BASE_URL}/refresh") + } pub fn search(query: &str) -> String { - format!("{}/search/{}", BASE_URL, query) + format!("{BASE_URL}/search/{query}") } pub fn show(tid: &str) -> String { - format!("{}/show/{}", BASE_URL, tid) + format!("{BASE_URL}/show/{tid}") } pub fn show_pretty(tid: &str) -> String { - format!("{}/show/{}/pretty", BASE_URL, tid) + format!("{BASE_URL}/show/{tid}/pretty") } pub fn original(message_id: &str) -> String { - format!("{}/original/{}", BASE_URL, message_id) + format!("{BASE_URL}/original/{message_id}") } } +async fn refresh_request() -> fetch::Result<()> { + let t = Request::new(api::refresh()) + .method(Method::Get) + .fetch() + .await? + .check_status()? + .text() + .await?; + info!("refresh {t}"); + Ok(()) +} + async fn show_request(tid: &str) -> fetch::Result { let b = Request::new(api::show(tid)) .method(Method::Get) @@ -346,6 +367,39 @@ fn pretty_authors(authors: &str) -> impl Iterator> + '_ { ) } +fn view_mobile_search_results(query: &str, search_results: &SearchSummary) -> Node { + if query.is_empty() { + set_title("all mail"); + } else { + set_title(query); + } + let rows = search_results.0.iter().map(|r| { + /* + let tid = r.thread.clone(); + tr![ + td![ + C!["from"], + pretty_authors(&r.authors), + IF!(r.total>1 => small![" ", r.total.to_string()]), + ], + td![C!["subject"], tags_chiclet(&r.tags), " ", &r.subject], + td![C!["date"], &r.date_relative], + ev(Ev::Click, move |_| Msg::ShowPrettyRequest(tid)), + ] + */ + div![ + p![C!["subject"], &r.subject], + div![ + span![C!["from"], pretty_authors(&r.authors)], + span![C!["tags"], tags_chiclet(&r.tags)], + ], + span![C!["date"], &r.date_relative], + hr![], + ] + }); + div![h1!["Search results"], rows] +} + fn view_search_results(query: &str, search_results: &SearchSummary) -> Node { if query.is_empty() { set_title("all mail"); @@ -439,7 +493,12 @@ fn view_header(query: &str) -> Node { div![ C!["navbar-start"], a![ - C!["navbar-item", "button",], + C!["navbar-item", "button"], + span![i![C!["fa-solid", "fa-arrow-rotate-right"]]], + ev(Ev::Click, |_| Msg::Refresh), + ], + a![ + C!["navbar-item", "button"], "Unread", ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())), ], @@ -477,10 +536,7 @@ fn view_footer(render_time_ms: u128) -> Node { ] } -// `view` describes what to display. -fn view(model: &Model) -> Node { - let start = Instant::now(); - info!("view called"); +fn view_desktop(model: &Model) -> Node { let content = match &model.context { Context::None => div![h1!["Loading"]], Context::Thread(thread_set) => view_thread(thread_set), @@ -488,11 +544,39 @@ fn view(model: &Model) -> Node { }; div![ view_header(&model.query), - section![ - C!["section"], - div![C!["container"], content], - view_footer(start.elapsed().as_millis()) - ] + section![C!["section"], div![C!["container"], content],] + ] +} + +fn view_mobile(model: &Model) -> Node { + let content = match &model.context { + Context::None => div![h1!["Loading"]], + Context::Thread(thread_set) => view_thread(thread_set), + Context::Search(search_results) => view_mobile_search_results(&model.query, search_results), + }; + div![ + view_header(&model.query), + section![C!["section"], div![C!["container"], content],] + ] +} + +// `view` describes what to display. +fn view(model: &Model) -> Node { + let is_mobile = seed::window() + .match_media("(max-width: 768px)") + .expect("failed media query") + .map(|mql| mql.matches()) + .unwrap_or(false); + + let start = Instant::now(); + info!("view called"); + div![ + if is_mobile { + view_mobile(model) + } else { + view_desktop(model) + }, + view_footer(start.elapsed().as_millis()) ] }