From 4faef5e0173faa9063cc8009b93e318559115956 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 31 Aug 2024 16:08:06 -0700 Subject: [PATCH] web: add scrollbar for read progress --- web/src/state.rs | 27 +++++++++++++++++++++++++++ web/src/view/desktop.rs | 3 ++- web/src/view/mobile.rs | 5 +++-- web/src/view/mod.rs | 31 +++++++++++++++++++++++++------ web/src/view/tablet.rs | 3 ++- web/static/style.css | 6 ++++++ 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/web/src/state.rs b/web/src/state.rs index 64e1c31..79e3f2e 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -37,15 +37,36 @@ 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() + })); Model { context: Context::None, query: "".to_string(), refreshing_state: RefreshingState::None, tags: None, + read_completion_ratio: 0., } } +fn compute_scroll_ratio() -> Msg { + // TODO: compute completion based on contents of the post, not the overall body (which includes + // the tags at the end on mobile/tablet). + let body = document().body().expect("body"); + let sh = body.scroll_height() as f64; + let window = window(); + let ih = window + .inner_height() + .expect("window height") + .unchecked_into::() + .value_of(); + let scroll_y = window.scroll_y().expect("scroll Y"); + + let end = sh - ih; + let ratio = scroll_y / end; + Msg::SetProgress(ratio) +} fn on_url_changed(uc: subs::UrlChanged) -> Msg { let mut url = uc.0; info!( @@ -489,6 +510,9 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders) { .expect("failed to copy to clipboard"); }); } + Msg::SetProgress(ratio) => { + model.read_completion_ratio = ratio; + } } } // `Model` describes our app state. @@ -497,6 +521,7 @@ pub struct Model { pub context: Context, pub refreshing_state: RefreshingState, pub tags: Option>, + pub read_completion_ratio: f64, } #[derive(Error, Debug)] @@ -590,4 +615,6 @@ pub enum Msg { MultiMsg(Vec), CopyToClipboard(String), + + SetProgress(f64), } diff --git a/web/src/view/desktop.rs b/web/src/view/desktop.rs index 84bc536..5acb56e 100644 --- a/web/src/view/desktop.rs +++ b/web/src/view/desktop.rs @@ -5,7 +5,7 @@ use crate::{ api::urls, graphql::show_thread_query::*, state::{Context, Model, Msg}, - view::{self, view_header, view_search_results, view_tags}, + view::{self, reading_progress, view_header, view_search_results, view_tags}, }; #[topo::nested] @@ -40,6 +40,7 @@ pub(super) fn view(model: &Model) -> Node { }; div![ C!["main-content"], + reading_progress(model.read_completion_ratio), view_tags(model), div![ view_header(&model.query, &model.refreshing_state), diff --git a/web/src/view/mobile.rs b/web/src/view/mobile.rs index 9be211b..0ec70b3 100644 --- a/web/src/view/mobile.rs +++ b/web/src/view/mobile.rs @@ -7,8 +7,8 @@ use crate::{ graphql::{front_page_query::*, show_thread_query::*}, state::{Context, Model, Msg}, view::{ - self, human_age, pretty_authors, search_toolbar, set_title, tags_chiclet, view_header, - view_tags, + self, human_age, pretty_authors, reading_progress, search_toolbar, set_title, tags_chiclet, + view_header, view_tags, }, }; @@ -41,6 +41,7 @@ pub(super) fn view(model: &Model) -> Node { ), }; div![ + reading_progress(model.read_completion_ratio), view_header(&model.query, &model.refreshing_state), content, view_header(&model.query, &model.refreshing_state), diff --git a/web/src/view/mod.rs b/web/src/view/mod.rs index bae68af..0bb3cbc 100644 --- a/web/src/view/mod.rs +++ b/web/src/view/mod.rs @@ -1117,12 +1117,14 @@ fn news_post(post: &ShowThreadQueryThreadOnNewsPost, show_icon_text: bool) -> No C!["body", "news-post", format!("site-{}", post.slug)], raw![&post.body] ] - ] /* TODO(wathiede): plumb in orignal id - a![ - attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, - "Original" - ], - */ + ], + /* TODO(wathiede): plumb in orignal id + a![ + attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, + "Original" + ], + */ + ev(Ev::Scroll, |e| info!("scroll event {e:?}")) ] } fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node { @@ -1181,3 +1183,20 @@ fn render_news_post_header(post: &ShowThreadQueryThreadOnNewsPost) -> Node ] ] } +fn reading_progress(ratio: f64) -> Node { + let percent = ratio * 100.; + progress![ + C![ + "read-progress", + "progress", + "is-success", + "is-small", + IF!(percent<5. => "is-invisible") + ], + attrs! { + At::Value=>percent, + At::Max=>"100" + }, + format!("{percent}%") + ] +} diff --git a/web/src/view/tablet.rs b/web/src/view/tablet.rs index f780691..5b02571 100644 --- a/web/src/view/tablet.rs +++ b/web/src/view/tablet.rs @@ -4,7 +4,7 @@ use seed::{prelude::*, *}; use crate::{ graphql::show_thread_query::*, state::{Context, Model, Msg}, - view::{self, view_header, view_search_results, view_tags}, + view::{self, reading_progress, view_header, view_search_results, view_tags}, }; pub(super) fn view(model: &Model) -> Node { @@ -39,6 +39,7 @@ pub(super) fn view(model: &Model) -> Node { div![ C!["main-content"], div![ + reading_progress(model.read_completion_ratio), view_header(&model.query, &model.refreshing_state), content, view_header(&model.query, &model.refreshing_state), diff --git a/web/static/style.css b/web/static/style.css index 2599748..25ca84e 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -343,3 +343,9 @@ display: none; .button.spam { color: #f00; } + +progress.read-progress { + position: fixed; + top: 0; + z-index: 999; +}