web: more accurate reading progress bar

This commit is contained in:
Bill Thiede 2024-09-09 20:21:13 -07:00
parent baba720749
commit 4b8923d852
7 changed files with 57 additions and 21 deletions

View File

@ -41,7 +41,9 @@ wasm-opt = ['-Os']
version = "0.3.58" version = "0.3.58"
features = [ features = [
"Clipboard", "Clipboard",
"DomRect",
"Element",
"MediaQueryList", "MediaQueryList",
"Navigator", "Navigator",
"Window" "Window",
] ]

View File

@ -4,6 +4,7 @@ use graphql_client::GraphQLQuery;
use log::{error, info, warn}; use log::{error, info, warn};
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use thiserror::Error; use thiserror::Error;
use web_sys::HtmlElement;
use crate::{ use crate::{
api, api,
@ -39,9 +40,7 @@ pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
// 'notmuch new' on the server periodically? // 'notmuch new' on the server periodically?
orders.stream(streams::interval(30_000, || Msg::RefreshStart)); orders.stream(streams::interval(30_000, || Msg::RefreshStart));
orders.subscribe(on_url_changed); orders.subscribe(on_url_changed);
orders.stream(streams::window_event(Ev::Scroll, |_| { orders.stream(streams::window_event(Ev::Scroll, |_| Msg::WindowScrolled));
compute_scroll_ratio()
}));
build_info::build_info!(fn bi); build_info::build_info!(fn bi);
Model { Model {
@ -50,6 +49,7 @@ pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
refreshing_state: RefreshingState::None, refreshing_state: RefreshingState::None,
tags: None, tags: None,
read_completion_ratio: 0., read_completion_ratio: 0.,
content_el: ElRef::<HtmlElement>::default(),
versions: Version { versions: Version {
client: version, client: version,
server: None, server: None,
@ -521,6 +521,27 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
.expect("failed to copy to clipboard"); .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::<js_sys::Number>()
.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) => { Msg::SetProgress(ratio) => {
model.read_completion_ratio = ratio; model.read_completion_ratio = ratio;
} }
@ -543,6 +564,7 @@ pub struct Model {
pub refreshing_state: RefreshingState, pub refreshing_state: RefreshingState,
pub tags: Option<Vec<Tag>>, pub tags: Option<Vec<Tag>>,
pub read_completion_ratio: f64, pub read_completion_ratio: f64,
pub content_el: ElRef<HtmlElement>,
pub versions: Version, pub versions: Version,
} }
@ -647,6 +669,7 @@ pub enum Msg {
CopyToClipboard(String), CopyToClipboard(String),
WindowScrolled,
SetProgress(f64), SetProgress(f64),
UpdateServerVersion(String), UpdateServerVersion(String),
} }

View File

@ -16,11 +16,11 @@ pub(super) fn view(model: &Model) -> Node<Msg> {
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::EmailThread(thread), thread: ShowThreadQueryThread::EmailThread(thread),
open_messages, open_messages,
} => view::thread(thread, open_messages, show_icon_text), } => view::thread(thread, open_messages, show_icon_text, &model.content_el),
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::NewsPost(post), thread: ShowThreadQueryThread::NewsPost(post),
.. ..
} => view::news_post(post, show_icon_text), } => view::news_post(post, show_icon_text, &model.content_el),
Context::SearchResult { Context::SearchResult {
query, query,
results, results,

View File

@ -19,11 +19,11 @@ pub(super) fn view(model: &Model) -> Node<Msg> {
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::EmailThread(thread), thread: ShowThreadQueryThread::EmailThread(thread),
open_messages, open_messages,
} => view::thread(thread, open_messages, show_icon_text), } => view::thread(thread, open_messages, show_icon_text, &model.content_el),
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::NewsPost(post), thread: ShowThreadQueryThread::NewsPost(post),
.. ..
} => view::news_post(post, show_icon_text), } => view::news_post(post, show_icon_text, &model.content_el),
Context::SearchResult { Context::SearchResult {
query, query,
results, results,

View File

@ -6,9 +6,10 @@ use std::{
use chrono::{DateTime, Datelike, Duration, Local, Utc}; use chrono::{DateTime, Datelike, Duration, Local, Utc};
use human_format::{Formatter, Scales}; use human_format::{Formatter, Scales};
use itertools::Itertools; use itertools::Itertools;
use log::{error, info}; use log::{debug, error, info};
use seed::{prelude::*, *}; use seed::{prelude::*, *};
use seed_hooks::{state_access::CloneState, topo, use_state}; use seed_hooks::{state_access::CloneState, topo, use_state};
use web_sys::HtmlElement;
use crate::{ use crate::{
api::urls, api::urls,
@ -795,6 +796,7 @@ fn thread(
thread: &ShowThreadQueryThreadOnEmailThread, thread: &ShowThreadQueryThreadOnEmailThread,
open_messages: &HashSet<String>, open_messages: &HashSet<String>,
show_icon_text: bool, show_icon_text: bool,
content_el: &ElRef<HtmlElement>,
) -> Node<Msg> { ) -> Node<Msg> {
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject // TODO(wathiede): show per-message subject if it changes significantly from top-level subject
let subject = if thread.subject.is_empty() { let subject = if thread.subject.is_empty() {
@ -867,8 +869,7 @@ fn thread(
], ],
], ],
], ],
messages, div![el_ref(content_el), messages] /* TODO(wathiede): plumb in orignal id
/* TODO(wathiede): plumb in orignal id
a![ a![
attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)}, attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)},
"Original" "Original"
@ -1074,7 +1075,11 @@ pub fn tags(model: &Model) -> Node<Msg> {
] ]
] ]
} }
fn news_post(post: &ShowThreadQueryThreadOnNewsPost, show_icon_text: bool) -> Node<Msg> { fn news_post(
post: &ShowThreadQueryThreadOnNewsPost,
show_icon_text: bool,
content_el: &ElRef<HtmlElement>,
) -> Node<Msg> {
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject // TODO(wathiede): show per-message subject if it changes significantly from top-level subject
let subject = &post.title; let subject = &post.title;
set_title(subject); 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!["header"], render_news_post_header(&post)],
div![ div![
C!["body", "news-post", format!("site-{}", post.slug)], C!["body", "news-post", format!("site-{}", post.slug)],
el_ref(content_el),
raw![&post.body] raw![&post.body]
] ]
], ],
@ -1190,7 +1196,7 @@ fn reading_progress(ratio: f64) -> Node<Msg> {
"progress", "progress",
"is-success", "is-success",
"is-small", "is-small",
IF!(percent<5. => "is-invisible") IF!(percent<1. => "is-invisible")
], ],
attrs! { attrs! {
At::Value=>percent, At::Value=>percent,
@ -1200,7 +1206,7 @@ fn reading_progress(ratio: f64) -> Node<Msg> {
] ]
} }
pub fn versions(versions: &crate::state::Version) -> Node<Msg> { pub fn versions(versions: &crate::state::Version) -> Node<Msg> {
info!("versions {versions:?}"); debug!("versions {versions:?}");
aside![ aside![
C!["tags-menu", "menu"], C!["tags-menu", "menu"],
p![C!["menu-label"], "Versions"], p![C!["menu-label"], "Versions"],

View File

@ -14,11 +14,11 @@ pub(super) fn view(model: &Model) -> Node<Msg> {
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::EmailThread(thread), thread: ShowThreadQueryThread::EmailThread(thread),
open_messages, open_messages,
} => view::thread(thread, open_messages, show_icon_text), } => view::thread(thread, open_messages, show_icon_text, &model.content_el),
Context::ThreadResult { Context::ThreadResult {
thread: ShowThreadQueryThread::NewsPost(post), thread: ShowThreadQueryThread::NewsPost(post),
.. ..
} => view::news_post(post, show_icon_text), } => view::news_post(post, show_icon_text, &model.content_el),
Context::SearchResult { Context::SearchResult {
query, query,
results, results,

View File

@ -345,7 +345,12 @@ display: none;
} }
progress.read-progress { progress.read-progress {
border-radius: 0;
position: fixed; position: fixed;
top: 0; top: 0;
z-index: 999; z-index: 999;
} }
progress.read-progress.is-small {
height: .25rem;
}