From 4b8923d85211f817a452d6ff3b23ab08faf6f0ff Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Mon, 9 Sep 2024 20:21:13 -0700 Subject: [PATCH] web: more accurate reading progress bar --- web/Cargo.toml | 4 +++- web/src/state.rs | 29 ++++++++++++++++++++++++++--- web/src/view/desktop.rs | 4 ++-- web/src/view/mobile.rs | 4 ++-- web/src/view/mod.rs | 28 +++++++++++++++++----------- web/src/view/tablet.rs | 4 ++-- web/static/style.css | 5 +++++ 7 files changed, 57 insertions(+), 21 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index 16c01ea..fb852d0 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -41,7 +41,9 @@ wasm-opt = ['-Os'] version = "0.3.58" features = [ "Clipboard", + "DomRect", + "Element", "MediaQueryList", "Navigator", - "Window" + "Window", ] diff --git a/web/src/state.rs b/web/src/state.rs index b407cca..99e9634 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -4,6 +4,7 @@ use graphql_client::GraphQLQuery; use log::{error, info, warn}; use seed::{prelude::*, *}; use thiserror::Error; +use web_sys::HtmlElement; use crate::{ api, @@ -39,9 +40,7 @@ pub fn init(url: Url, orders: &mut impl Orders) -> Model { // 'notmuch new' on the server periodically? orders.stream(streams::interval(30_000, || Msg::RefreshStart)); orders.subscribe(on_url_changed); - orders.stream(streams::window_event(Ev::Scroll, |_| { - compute_scroll_ratio() - })); + orders.stream(streams::window_event(Ev::Scroll, |_| Msg::WindowScrolled)); build_info::build_info!(fn bi); Model { @@ -50,6 +49,7 @@ pub fn init(url: Url, orders: &mut impl Orders) -> Model { refreshing_state: RefreshingState::None, tags: None, read_completion_ratio: 0., + content_el: ElRef::::default(), versions: Version { client: version, server: None, @@ -521,6 +521,27 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { .expect("failed to copy to clipboard"); }); } + Msg::WindowScrolled => { + if let Some(el) = model.content_el.get() { + let ih = window() + .inner_height() + .expect("window height") + .unchecked_into::() + .value_of(); + + let r = el.get_bounding_client_rect(); + info!( + "window scrolled {}x{}@{},{}", + r.width(), + r.height(), + r.x(), + r.y(), + ); + let end = r.height() - ih; + let y = -r.y(); + orders.send_msg(Msg::SetProgress((y / end).max(0.))); + } + } Msg::SetProgress(ratio) => { model.read_completion_ratio = ratio; } @@ -543,6 +564,7 @@ pub struct Model { pub refreshing_state: RefreshingState, pub tags: Option>, pub read_completion_ratio: f64, + pub content_el: ElRef, pub versions: Version, } @@ -647,6 +669,7 @@ pub enum Msg { CopyToClipboard(String), + WindowScrolled, SetProgress(f64), UpdateServerVersion(String), } diff --git a/web/src/view/desktop.rs b/web/src/view/desktop.rs index 2babae6..d3ee029 100644 --- a/web/src/view/desktop.rs +++ b/web/src/view/desktop.rs @@ -16,11 +16,11 @@ pub(super) fn view(model: &Model) -> Node { Context::ThreadResult { thread: ShowThreadQueryThread::EmailThread(thread), open_messages, - } => view::thread(thread, open_messages, show_icon_text), + } => view::thread(thread, open_messages, show_icon_text, &model.content_el), Context::ThreadResult { thread: ShowThreadQueryThread::NewsPost(post), .. - } => view::news_post(post, show_icon_text), + } => view::news_post(post, show_icon_text, &model.content_el), Context::SearchResult { query, results, diff --git a/web/src/view/mobile.rs b/web/src/view/mobile.rs index fcab129..e8f5bd0 100644 --- a/web/src/view/mobile.rs +++ b/web/src/view/mobile.rs @@ -19,11 +19,11 @@ pub(super) fn view(model: &Model) -> Node { Context::ThreadResult { thread: ShowThreadQueryThread::EmailThread(thread), open_messages, - } => view::thread(thread, open_messages, show_icon_text), + } => view::thread(thread, open_messages, show_icon_text, &model.content_el), Context::ThreadResult { thread: ShowThreadQueryThread::NewsPost(post), .. - } => view::news_post(post, show_icon_text), + } => view::news_post(post, show_icon_text, &model.content_el), Context::SearchResult { query, results, diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index 9427494..d8e2961 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -6,9 +6,10 @@ use std::{ use chrono::{DateTime, Datelike, Duration, Local, Utc}; use human_format::{Formatter, Scales}; use itertools::Itertools; -use log::{error, info}; +use log::{debug, error, info}; use seed::{prelude::*, *}; use seed_hooks::{state_access::CloneState, topo, use_state}; +use web_sys::HtmlElement; use crate::{ api::urls, @@ -795,6 +796,7 @@ fn thread( thread: &ShowThreadQueryThreadOnEmailThread, open_messages: &HashSet, show_icon_text: bool, + content_el: &ElRef, ) -> Node { // TODO(wathiede): show per-message subject if it changes significantly from top-level subject let subject = if thread.subject.is_empty() { @@ -867,13 +869,12 @@ fn thread( ], ], ], - messages, - /* TODO(wathiede): plumb in orignal id - a![ - attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, - "Original" - ], - */ + div![el_ref(content_el), messages] /* TODO(wathiede): plumb in orignal id + a![ + attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, + "Original" + ], + */ ] } @@ -1074,7 +1075,11 @@ pub fn tags(model: &Model) -> Node { ] ] } -fn news_post(post: &ShowThreadQueryThreadOnNewsPost, show_icon_text: bool) -> Node { +fn news_post( + post: &ShowThreadQueryThreadOnNewsPost, + show_icon_text: bool, + content_el: &ElRef, +) -> Node { // TODO(wathiede): show per-message subject if it changes significantly from top-level subject let subject = &post.title; set_title(subject); @@ -1114,6 +1119,7 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, show_icon_text: bool) -> No div![C!["header"], render_news_post_header(&post)], div![ C!["body", "news-post", format!("site-{}", post.slug)], + el_ref(content_el), raw![&post.body] ] ], @@ -1190,7 +1196,7 @@ fn reading_progress(ratio: f64) -> Node { "progress", "is-success", "is-small", - IF!(percent<5. => "is-invisible") + IF!(percent<1. => "is-invisible") ], attrs! { At::Value=>percent, @@ -1200,7 +1206,7 @@ fn reading_progress(ratio: f64) -> Node { ] } pub fn versions(versions: &crate::state::Version) -> Node { - info!("versions {versions:?}"); + debug!("versions {versions:?}"); aside![ C!["tags-menu", "menu"], p![C!["menu-label"], "Versions"], diff --git a/web/src/view/tablet.rs b/web/src/view/tablet.rs index e3b0b8d..24de306 100644 --- a/web/src/view/tablet.rs +++ b/web/src/view/tablet.rs @@ -14,11 +14,11 @@ pub(super) fn view(model: &Model) -> Node { Context::ThreadResult { thread: ShowThreadQueryThread::EmailThread(thread), open_messages, - } => view::thread(thread, open_messages, show_icon_text), + } => view::thread(thread, open_messages, show_icon_text, &model.content_el), Context::ThreadResult { thread: ShowThreadQueryThread::NewsPost(post), .. - } => view::news_post(post, show_icon_text), + } => view::news_post(post, show_icon_text, &model.content_el), Context::SearchResult { query, results, diff --git a/web/static/style.css b/web/static/style.css index 25ca84e..34cd007 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -345,7 +345,12 @@ display: none; } progress.read-progress { + border-radius: 0; position: fixed; top: 0; z-index: 999; } + +progress.read-progress.is-small { + height: .25rem; +}