use seed::{prelude::*, *}; use seed_hooks::{state_access::CloneState, topo, use_state}; use crate::{ api::urls, state::{Context, Model, Msg, Tag}, view::{self, view_header, view_search_results}, }; #[topo::nested] pub(super) fn view(model: &Model) -> Node { log::info!("tablet::view"); 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, open_messages, } => view::thread(thread, open_messages, show_icon_text), Context::SearchResult { query, results, count, pager, selected_threads, } => view_search_results( &query, results.as_slice(), *count, pager, selected_threads, show_icon_text, ), }; fn view_tag_li(display_name: &str, indent: usize, t: &Tag, search_unread: bool) -> Node { let href = if search_unread { urls::search(&format!("is:unread tag:{}", t.name), 0) } else { urls::search(&format!("tag:{}", t.name), 0) }; li![a![ attrs! { At::Href => href }, (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, search_unread: bool, ) -> Vec> { 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 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, }, search_unread, )); } n = parts.len() - 1; } let display_name = parts[n]; lis.push(view_tag_li(&display_name, n, t, search_unread)); 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!["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(),true)]), 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(),false))), ] ], div![ view_header(&model.query, &model.refreshing_state), content, view_header(&model.query, &model.refreshing_state), ] ] }