web: only use one view function, desktop/tablet/mobile handled in CSS
This commit is contained in:
parent
ea280dd366
commit
25d31a6ce7
@ -507,6 +507,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
Msg::WindowScrolled => {
|
Msg::WindowScrolled => {
|
||||||
|
info!("WindowScrolled");
|
||||||
if let Some(el) = model.content_el.get() {
|
if let Some(el) = model.content_el.get() {
|
||||||
let ih = window()
|
let ih = window()
|
||||||
.inner_height()
|
.inner_height()
|
||||||
@ -515,6 +516,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
.value_of();
|
.value_of();
|
||||||
|
|
||||||
let r = el.get_bounding_client_rect();
|
let r = el.get_bounding_client_rect();
|
||||||
|
info!("r {r:?} ih {ih}");
|
||||||
if r.height() < ih {
|
if r.height() < ih {
|
||||||
// The whole content fits in the window, no scrollbar
|
// The whole content fits in the window, no scrollbar
|
||||||
orders.send_msg(Msg::SetProgress(0.));
|
orders.send_msg(Msg::SetProgress(0.));
|
||||||
|
|||||||
@ -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<Msg> {
|
|
||||||
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),
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -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<Msg> {
|
|
||||||
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<String>,
|
|
||||||
show_icon_text: bool,
|
|
||||||
) -> Node<Msg> {
|
|
||||||
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::<web_sys::HtmlInputElement>()
|
|
||||||
{
|
|
||||||
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),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -12,17 +12,140 @@ use web_sys::HtmlElement;
|
|||||||
use crate::{
|
use crate::{
|
||||||
api::urls,
|
api::urls,
|
||||||
graphql::{front_page_query::*, show_thread_query::*},
|
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
|
// 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
|
// 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.
|
// format!() calls all over with magic strings representing notmuch specific syntax.
|
||||||
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
|
const MAX_RAW_MESSAGE_SIZE: usize = 100_000;
|
||||||
|
|
||||||
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
|
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<String>,
|
||||||
|
show_icon_text: bool,
|
||||||
|
) -> Node<Msg> {
|
||||||
|
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::<web_sys::HtmlInputElement>()
|
||||||
|
{
|
||||||
|
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) {
|
fn set_title(title: &str) {
|
||||||
seed::document().set_title(&format!("lb: {}", title));
|
seed::document().set_title(&format!("lb: {}", title));
|
||||||
}
|
}
|
||||||
@ -916,25 +1039,6 @@ fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// `view` describes what to display.
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
|
||||||
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<Msg> {
|
pub fn tags(model: &Model) -> Node<Msg> {
|
||||||
fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node<Msg> {
|
fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node<Msg> {
|
||||||
let href = if search_unread {
|
let href = if search_unread {
|
||||||
@ -977,7 +1081,7 @@ pub fn tags(model: &Model) -> Node<Msg> {
|
|||||||
for t in tags {
|
for t in tags {
|
||||||
let parts: Vec<_> = t.name.split('/').collect();
|
let parts: Vec<_> = t.name.split('/').collect();
|
||||||
let mut n = matches(&last, &parts);
|
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.
|
// Synthesize fake tags for proper indenting.
|
||||||
for i in n..parts.len() - 1 {
|
for i in n..parts.len() - 1 {
|
||||||
let display_name = parts[n];
|
let display_name = parts[n];
|
||||||
|
|||||||
@ -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<Msg> {
|
|
||||||
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)
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user