web: handle expand/collapse of messages separate from unread status
This commit is contained in:
parent
fe980c5468
commit
5923547159
@ -119,7 +119,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Context::ThreadResult(_) => (), // do nothing (yet?)
|
Context::ThreadResult { .. } => (), // do nothing (yet?)
|
||||||
Context::None => (), // do nothing (yet?)
|
Context::None => (), // do nothing (yet?)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Context::ThreadResult(_) => (), // do nothing (yet?)
|
Context::ThreadResult { .. } => (), // do nothing (yet?)
|
||||||
Context::None => (), // do nothing (yet?)
|
Context::None => (), // do nothing (yet?)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -254,7 +254,25 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
model.context = Context::ThreadResult(data.thread);
|
let mut open_messages: HashSet<_> = data
|
||||||
|
.thread
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.filter(|msg| msg.tags.iter().any(|t| t == "unread"))
|
||||||
|
.map(|msg| msg.id.clone())
|
||||||
|
.collect();
|
||||||
|
if open_messages.is_empty() {
|
||||||
|
open_messages = data
|
||||||
|
.thread
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.map(|msg| msg.id.clone())
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
model.context = Context::ThreadResult {
|
||||||
|
thread: data.thread,
|
||||||
|
open_messages,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Msg::ShowThreadResult(bad) => {
|
Msg::ShowThreadResult(bad) => {
|
||||||
error!("show_thread_query error: {bad:#?}");
|
error!("show_thread_query error: {bad:#?}");
|
||||||
@ -307,6 +325,18 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
|||||||
info!("selected threads {selected_threads:?}");
|
info!("selected threads {selected_threads:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Msg::MessageCollapse(id) => {
|
||||||
|
if let Context::ThreadResult { open_messages, .. } = &mut model.context {
|
||||||
|
open_messages.remove(&id);
|
||||||
|
info!("open_messages threads {open_messages:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Msg::MessageExpand(id) => {
|
||||||
|
if let Context::ThreadResult { open_messages, .. } = &mut model.context {
|
||||||
|
open_messages.insert(id);
|
||||||
|
info!("open_messages threads {open_messages:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// `Model` describes our app state.
|
// `Model` describes our app state.
|
||||||
@ -340,7 +370,10 @@ pub enum Context {
|
|||||||
pager: FrontPageQuerySearchPageInfo,
|
pager: FrontPageQuerySearchPageInfo,
|
||||||
selected_threads: HashSet<String>,
|
selected_threads: HashSet<String>,
|
||||||
},
|
},
|
||||||
ThreadResult(ShowThreadQueryThread),
|
ThreadResult {
|
||||||
|
thread: ShowThreadQueryThread,
|
||||||
|
open_messages: HashSet<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
@ -394,4 +427,7 @@ pub enum Msg {
|
|||||||
SelectionMarkAsUnread,
|
SelectionMarkAsUnread,
|
||||||
SelectionAddThread(String),
|
SelectionAddThread(String),
|
||||||
SelectionRemoveThread(String),
|
SelectionRemoveThread(String),
|
||||||
|
|
||||||
|
MessageCollapse(String),
|
||||||
|
MessageExpand(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,10 @@ pub(super) fn view(model: &Model) -> Node<Msg> {
|
|||||||
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::ThreadResult(thread) => view::thread(thread),
|
Context::ThreadResult {
|
||||||
|
thread,
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages),
|
||||||
Context::SearchResult {
|
Context::SearchResult {
|
||||||
query,
|
query,
|
||||||
results,
|
results,
|
||||||
|
|||||||
@ -12,7 +12,10 @@ use crate::{
|
|||||||
pub(super) fn view(model: &Model) -> Node<Msg> {
|
pub(super) fn view(model: &Model) -> Node<Msg> {
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::ThreadResult(thread) => view::thread(thread),
|
Context::ThreadResult {
|
||||||
|
thread,
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages),
|
||||||
Context::SearchResult {
|
Context::SearchResult {
|
||||||
query,
|
query,
|
||||||
results,
|
results,
|
||||||
|
|||||||
@ -7,10 +7,7 @@ use chrono::{DateTime, Datelike, Duration, Local, Utc};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::error;
|
use log::error;
|
||||||
use seed::{prelude::*, *};
|
use seed::{prelude::*, *};
|
||||||
use seed_hooks::{
|
use seed_hooks::{state_access::CloneState, topo, use_state};
|
||||||
state_access::{CloneState, StateAccess},
|
|
||||||
topo, use_state,
|
|
||||||
};
|
|
||||||
use wasm_timer::Instant;
|
use wasm_timer::Instant;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -333,8 +330,9 @@ fn has_unread(tags: &[String]) -> bool {
|
|||||||
tags.contains(&String::from("unread"))
|
tags.contains(&String::from("unread"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_message_render(msg: &ShowThreadQueryThreadMessages, open: StateAccess<bool>) -> Node<Msg> {
|
fn read_message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node<Msg> {
|
||||||
let id = msg.id.clone();
|
let id = msg.id.clone();
|
||||||
|
let expand_id = msg.id.clone();
|
||||||
let is_unread = has_unread(&msg.tags);
|
let is_unread = has_unread(&msg.tags);
|
||||||
div![
|
div![
|
||||||
C!["message"],
|
C!["message"],
|
||||||
@ -365,16 +363,18 @@ fn read_message_render(msg: &ShowThreadQueryThreadMessages, open: StateAccess<bo
|
|||||||
// TODO(wathiede): add first line of message body
|
// TODO(wathiede): add first line of message body
|
||||||
],
|
],
|
||||||
ev(Ev::Click, move |e| {
|
ev(Ev::Click, move |e| {
|
||||||
open.set(!open.get());
|
|
||||||
e.stop_propagation();
|
e.stop_propagation();
|
||||||
|
if open {
|
||||||
|
Msg::MessageCollapse(expand_id)
|
||||||
|
} else {
|
||||||
|
Msg::MessageExpand(expand_id)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn unread_message_render(
|
fn unread_message_render(msg: &ShowThreadQueryThreadMessages, open: bool) -> Node<Msg> {
|
||||||
msg: &ShowThreadQueryThreadMessages,
|
|
||||||
open: StateAccess<bool>,
|
|
||||||
) -> Node<Msg> {
|
|
||||||
let id = msg.id.clone();
|
let id = msg.id.clone();
|
||||||
|
let expand_id = msg.id.clone();
|
||||||
let is_unread = has_unread(&msg.tags);
|
let is_unread = has_unread(&msg.tags);
|
||||||
div![
|
div![
|
||||||
C!["message"],
|
C!["message"],
|
||||||
@ -408,8 +408,12 @@ fn unread_message_render(
|
|||||||
IF!(!msg.cc.is_empty() => span!["CC: ", view_addresses(&msg.cc)])
|
IF!(!msg.cc.is_empty() => span!["CC: ", view_addresses(&msg.cc)])
|
||||||
],
|
],
|
||||||
ev(Ev::Click, move |e| {
|
ev(Ev::Click, move |e| {
|
||||||
open.set(!open.get());
|
|
||||||
e.stop_propagation();
|
e.stop_propagation();
|
||||||
|
if open {
|
||||||
|
Msg::MessageCollapse(expand_id)
|
||||||
|
} else {
|
||||||
|
Msg::MessageExpand(expand_id)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
div![
|
div![
|
||||||
@ -452,7 +456,7 @@ fn unread_message_render(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[topo::nested]
|
#[topo::nested]
|
||||||
fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
fn thread(thread: &ShowThreadQueryThread, open_messages: &HashSet<String>) -> 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
|
||||||
set_title(&thread.subject);
|
set_title(&thread.subject);
|
||||||
let mut tags: Vec<_> = thread
|
let mut tags: Vec<_> = thread
|
||||||
@ -467,40 +471,39 @@ fn thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
|||||||
tags.sort();
|
tags.sort();
|
||||||
let messages = thread.messages.iter().map(|msg| {
|
let messages = thread.messages.iter().map(|msg| {
|
||||||
let is_unread = has_unread(&msg.tags);
|
let is_unread = has_unread(&msg.tags);
|
||||||
let open = use_state(|| is_unread);
|
let open = open_messages.contains(&msg.id);
|
||||||
//info!("open {} {}", open.get(), msg.id);
|
if open {
|
||||||
if open.get() {
|
|
||||||
unread_message_render(&msg, open)
|
unread_message_render(&msg, open)
|
||||||
} else {
|
} else {
|
||||||
read_message_render(&msg, open)
|
read_message_render(&msg, open)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let any_unread = thread.messages.iter().any(|msg| has_unread(&msg.tags));
|
let read_thread_id = thread.thread_id.clone();
|
||||||
let thread_id = thread.thread_id.clone();
|
let unread_thread_id = thread.thread_id.clone();
|
||||||
div![
|
div![
|
||||||
C!["thread"],
|
C!["thread"],
|
||||||
h1![
|
h3![C!["is-size-5"], &thread.subject,],
|
||||||
C!["title"],
|
tags_chiclet(&tags, false),
|
||||||
span![
|
span![
|
||||||
C!["read-status"],
|
C!["level-item", "buttons"],
|
||||||
i![
|
button![
|
||||||
style! {
|
C!["button"],
|
||||||
St::Color => "gold"
|
attrs! {At::Title => "Mark as read"},
|
||||||
},
|
span![C!["icon", "is-small"], i![C!["far", "fa-envelope-open"]]],
|
||||||
C![
|
|
||||||
if any_unread { "fa-regular" } else { "fa-solid" },
|
|
||||||
"fa-envelope"
|
|
||||||
],
|
|
||||||
ev(Ev::Click, move |_| Msg::SetUnread(
|
ev(Ev::Click, move |_| Msg::SetUnread(
|
||||||
format!("thread:{}", thread_id),
|
format!("thread:{read_thread_id}"),
|
||||||
!any_unread
|
false
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
" ",
|
button![
|
||||||
|
C!["button"],
|
||||||
|
attrs! {At::Title => "Mark as unread"},
|
||||||
|
span![C!["icon", "is-small"], i![C!["far", "fa-envelope"]]],
|
||||||
|
ev(Ev::Click, move |_| Msg::SetUnread(
|
||||||
|
format!("thread:{unread_thread_id}"),
|
||||||
|
true
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
&thread.subject,
|
|
||||||
" ",
|
|
||||||
tags_chiclet(&tags, false)
|
|
||||||
],
|
],
|
||||||
messages,
|
messages,
|
||||||
/* TODO(wathiede): plumb in orignal id
|
/* TODO(wathiede): plumb in orignal id
|
||||||
|
|||||||
@ -9,7 +9,10 @@ pub(super) fn view(model: &Model) -> Node<Msg> {
|
|||||||
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
// Do two queries, one without `unread` so it loads fast, then a second with unread.
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::ThreadResult(thread) => view::thread(thread),
|
Context::ThreadResult {
|
||||||
|
thread,
|
||||||
|
open_messages,
|
||||||
|
} => view::thread(thread, open_messages),
|
||||||
Context::SearchResult {
|
Context::SearchResult {
|
||||||
query,
|
query,
|
||||||
results,
|
results,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user