web: move desktop specific code into separate mod
This commit is contained in:
parent
12f0491455
commit
970bb55c73
124
web/src/view/desktop.rs
Normal file
124
web/src/view/desktop.rs
Normal 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),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -8,7 +8,6 @@ use itertools::Itertools;
|
|||||||
use log::info;
|
use log::info;
|
||||||
use notmuch::{Content, Part, ThreadNode, ThreadSet};
|
use notmuch::{Content, Part, ThreadNode, ThreadSet};
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
use seed_hooks::{state_access::CloneState, topo, use_state};
|
|
||||||
use wasm_timer::Instant;
|
use wasm_timer::Instant;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -16,9 +15,11 @@ use crate::{
|
|||||||
api::urls,
|
api::urls,
|
||||||
consts::{SEARCH_RESULTS_PER_PAGE, USE_GRAPHQL},
|
consts::{SEARCH_RESULTS_PER_PAGE, USE_GRAPHQL},
|
||||||
graphql::{front_page_query::*, show_thread_query::*},
|
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> {
|
fn view_message(thread: &ThreadNode) -> Node<Msg> {
|
||||||
let message = thread.0.as_ref().expect("ThreadNode missing Message");
|
let message = thread.0.as_ref().expect("ThreadNode missing Message");
|
||||||
let children = &thread.1;
|
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> {
|
fn view_mobile(model: &Model) -> Node<Msg> {
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
@ -841,7 +730,7 @@ pub fn view(model: &Model) -> Node<Msg> {
|
|||||||
if is_mobile {
|
if is_mobile {
|
||||||
view_mobile(model)
|
view_mobile(model)
|
||||||
} else {
|
} else {
|
||||||
view_desktop(model)
|
desktop::view(model)
|
||||||
},
|
},
|
||||||
view_footer(start.elapsed().as_millis())
|
view_footer(start.elapsed().as_millis())
|
||||||
]
|
]
|
||||||
Loading…
x
Reference in New Issue
Block a user