web: move desktop specific code into separate mod

This commit is contained in:
Bill Thiede 2023-12-02 09:56:57 -08:00
parent 12f0491455
commit 970bb55c73
2 changed files with 128 additions and 115 deletions

124
web/src/view/desktop.rs Normal file
View File

@ -0,0 +1,124 @@
use log::info;
use seed::{prelude::*, *};
use seed_hooks::{state_access::CloneState, topo, use_state};
use crate::{
api::urls,
state::{Context, Model, Msg, Tag},
view::{
view_header, view_search_results, view_search_results_legacy, view_thread,
view_thread_legacy,
},
};
#[topo::nested]
pub(super) fn view(model: &Model) -> Node<Msg> {
// 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::Thread(thread_set) => view_thread_legacy(thread_set),
Context::ThreadResult(thread) => view_thread(thread),
Context::Search(search_results) => view_search_results_legacy(&model.query, search_results),
Context::SearchResult {
query,
results,
count,
pager,
} => view_search_results(&query, results.as_slice(), *count, pager),
};
fn view_tag_li(display_name: &str, indent: usize, t: &Tag) -> Node<Msg> {
li![a![
attrs! {
At::Href => urls::search(&format!("tag:{}", t.name), 0)
},
(0..indent).map(|_| span![C!["tag-indent"], ""]),
i![
C!["tag-tag", "fa-solid", "fa-tag"],
style! {
//"--fa-primary-color" => t.fg_color,
St::Color => t.bg_color,
},
],
display_name,
IF!(t.unread>0 => format!(" ({})", t.unread))
]]
}
fn matches(a: &[&str], b: &[&str]) -> usize {
std::iter::zip(a.iter(), b.iter())
.take_while(|(a, b)| a == b)
.count()
}
fn view_tag_list<'a>(tags: impl Iterator<Item = &'a Tag>) -> Vec<Node<Msg>> {
let mut lis = Vec::new();
let mut last = Vec::new();
for t in tags {
let parts: Vec<_> = t.name.split('/').collect();
let mut n = matches(&last, &parts);
if t.name.starts_with("ZZCrap/Free") {
info!("n: {n}, parts: {parts:?} last: {last:?}");
}
if n <= parts.len() - 2 && parts.len() > 1 {
// Synthesize fake tags for proper indenting.
for i in n..parts.len() - 1 {
let display_name = parts[n];
lis.push(view_tag_li(
&display_name,
n,
&Tag {
name: parts[..i + 1].join("/"),
bg_color: "#fff".to_string(),
fg_color: "#000".to_string(),
unread: 0,
},
));
}
last = parts[..parts.len() - 1].to_vec();
n = parts.len() - 1;
}
let display_name = parts[n];
lis.push(view_tag_li(&display_name, n, t));
last = parts;
}
lis
}
let unread = model
.tags
.as_ref()
.map(|tags| tags.iter().filter(|t| t.unread > 0).collect())
.unwrap_or(Vec::new());
let tags_open = use_state(|| false);
let force_tags_open = unread.is_empty();
div![
C!["desktop-main-content"],
aside![
C!["tags-menu", "menu"],
IF!(!unread.is_empty() => p![C!["menu-label"], "Unread"]),
IF!(!unread.is_empty() => ul![C!["menu-list"], view_tag_list(unread.into_iter())]),
p![
C!["menu-label"],
IF!(!force_tags_open =>
i![C![
"fa-solid",
if tags_open.get() {
"fa-angle-up"
} else {
"fa-angle-down"
}
]]),
" Tags",
ev(Ev::Click, move |_| {
tags_open.set(!tags_open.get());
})
],
ul![
C!["menu-list"],
IF!(force_tags_open||tags_open.get() => model.tags.as_ref().map(|tags| view_tag_list(tags.iter()))),
]
],
div![
view_header(&model.query, &model.refreshing_state),
content,
view_header(&model.query, &model.refreshing_state),
]
]
}

View File

@ -8,7 +8,6 @@ use itertools::Itertools;
use log::info;
use notmuch::{Content, Part, ThreadNode, ThreadSet};
use seed::{prelude::*, *};
use seed_hooks::{state_access::CloneState, topo, use_state};
use wasm_timer::Instant;
use crate::{
@ -16,9 +15,11 @@ use crate::{
api::urls,
consts::{SEARCH_RESULTS_PER_PAGE, USE_GRAPHQL},
graphql::{front_page_query::*, show_thread_query::*},
state::{Context, Model, Msg, RefreshingState, Tag},
state::{Context, Model, Msg, RefreshingState},
};
mod desktop;
fn view_message(thread: &ThreadNode) -> Node<Msg> {
let message = thread.0.as_ref().expect("ThreadNode missing Message");
let children = &thread.1;
@ -687,118 +688,6 @@ fn view_footer(render_time_ms: u128) -> Node<Msg> {
]
}
#[topo::nested]
fn view_desktop(model: &Model) -> Node<Msg> {
// 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::Thread(thread_set) => view_thread_legacy(thread_set),
Context::ThreadResult(thread) => view_thread(thread),
Context::Search(search_results) => view_search_results_legacy(&model.query, search_results),
Context::SearchResult {
query,
results,
count,
pager,
} => view_search_results(&query, results.as_slice(), *count, pager),
};
fn view_tag_li(display_name: &str, indent: usize, t: &Tag) -> Node<Msg> {
li![a![
attrs! {
At::Href => urls::search(&format!("tag:{}", t.name), 0)
},
(0..indent).map(|_| span![C!["tag-indent"], ""]),
i![
C!["tag-tag", "fa-solid", "fa-tag"],
style! {
//"--fa-primary-color" => t.fg_color,
St::Color => t.bg_color,
},
],
display_name,
IF!(t.unread>0 => format!(" ({})", t.unread))
]]
}
fn matches(a: &[&str], b: &[&str]) -> usize {
std::iter::zip(a.iter(), b.iter())
.take_while(|(a, b)| a == b)
.count()
}
fn view_tag_list<'a>(tags: impl Iterator<Item = &'a Tag>) -> Vec<Node<Msg>> {
let mut lis = Vec::new();
let mut last = Vec::new();
for t in tags {
let parts: Vec<_> = t.name.split('/').collect();
let mut n = matches(&last, &parts);
if t.name.starts_with("ZZCrap/Free") {
info!("n: {n}, parts: {parts:?} last: {last:?}");
}
if n <= parts.len() - 2 && parts.len() > 1 {
// Synthesize fake tags for proper indenting.
for i in n..parts.len() - 1 {
let display_name = parts[n];
lis.push(view_tag_li(
&display_name,
n,
&Tag {
name: parts[..i + 1].join("/"),
bg_color: "#fff".to_string(),
fg_color: "#000".to_string(),
unread: 0,
},
));
}
last = parts[..parts.len() - 1].to_vec();
n = parts.len() - 1;
}
let display_name = parts[n];
lis.push(view_tag_li(&display_name, n, t));
last = parts;
}
lis
}
let unread = model
.tags
.as_ref()
.map(|tags| tags.iter().filter(|t| t.unread > 0).collect())
.unwrap_or(Vec::new());
let tags_open = use_state(|| false);
let force_tags_open = unread.is_empty();
div![
C!["desktop-main-content"],
aside![
C!["tags-menu", "menu"],
IF!(!unread.is_empty() => p![C!["menu-label"], "Unread"]),
IF!(!unread.is_empty() => ul![C!["menu-list"], view_tag_list(unread.into_iter())]),
p![
C!["menu-label"],
IF!(!force_tags_open =>
i![C![
"fa-solid",
if tags_open.get() {
"fa-angle-up"
} else {
"fa-angle-down"
}
]]),
" Tags",
ev(Ev::Click, move |_| {
tags_open.set(!tags_open.get());
})
],
ul![
C!["menu-list"],
IF!(force_tags_open||tags_open.get() => model.tags.as_ref().map(|tags| view_tag_list(tags.iter()))),
]
],
div![
view_header(&model.query, &model.refreshing_state),
content,
view_header(&model.query, &model.refreshing_state),
]
]
}
fn view_mobile(model: &Model) -> Node<Msg> {
let content = match &model.context {
Context::None => div![h1!["Loading"]],
@ -841,7 +730,7 @@ pub fn view(model: &Model) -> Node<Msg> {
if is_mobile {
view_mobile(model)
} else {
view_desktop(model)
desktop::view(model)
},
view_footer(start.elapsed().as_millis())
]