web: add basic graphql view thread, no body support.
This commit is contained in:
@@ -3,7 +3,7 @@ use seed::{
|
||||
fetch,
|
||||
fetch::{Header, Method, Request},
|
||||
};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
// The paths are relative to the directory where your `Cargo.toml` is located.
|
||||
// Both json and the GraphQL schema language are supported as sources for the schema
|
||||
@@ -15,6 +15,14 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
)]
|
||||
pub struct FrontPageQuery;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "graphql/schema.json",
|
||||
query_path = "graphql/show_thread.graphql",
|
||||
response_derives = "Debug"
|
||||
)]
|
||||
pub struct ShowThreadQuery;
|
||||
|
||||
pub async fn send_graphql<Body, Resp>(body: Body) -> fetch::Result<graphql_client::Response<Resp>>
|
||||
where
|
||||
Body: Serialize,
|
||||
|
||||
157
web/src/lib.rs
157
web/src/lib.rs
@@ -17,7 +17,7 @@ use serde::de::Deserialize;
|
||||
use thiserror::Error;
|
||||
use wasm_timer::Instant;
|
||||
|
||||
use crate::graphql::{front_page_query::*, send_graphql};
|
||||
use crate::graphql::{front_page_query::*, send_graphql, show_thread_query::*};
|
||||
|
||||
mod graphql;
|
||||
|
||||
@@ -67,7 +67,15 @@ fn on_url_changed(uc: subs::UrlChanged) -> Msg {
|
||||
);
|
||||
let hpp = url.remaining_hash_path_parts();
|
||||
match hpp.as_slice() {
|
||||
["t", tid] => Msg::ShowPrettyRequest(tid.to_string()),
|
||||
["t", tid] => {
|
||||
if USE_GRAPHQL {
|
||||
Msg::ShowThreadRequest {
|
||||
thread_id: tid.to_string(),
|
||||
}
|
||||
} else {
|
||||
Msg::ShowPrettyRequest(tid.to_string())
|
||||
}
|
||||
}
|
||||
["s", query] => {
|
||||
let query = Url::decode_uri_component(query).unwrap_or("".to_string());
|
||||
if USE_GRAPHQL {
|
||||
@@ -156,6 +164,7 @@ enum Context {
|
||||
pager: FrontPageQuerySearchPageInfo,
|
||||
},
|
||||
Thread(ThreadSet),
|
||||
ThreadResult(ShowThreadQueryThread),
|
||||
}
|
||||
|
||||
// `Model` describes our app state.
|
||||
@@ -210,6 +219,12 @@ enum Msg {
|
||||
FrontPageResult(
|
||||
fetch::Result<graphql_client::Response<graphql::front_page_query::ResponseData>>,
|
||||
),
|
||||
ShowThreadRequest {
|
||||
thread_id: String,
|
||||
},
|
||||
ShowThreadResult(
|
||||
fetch::Result<graphql_client::Response<graphql::show_thread_query::ResponseData>>,
|
||||
),
|
||||
}
|
||||
|
||||
// `update` describes how to handle each `Msg`.
|
||||
@@ -294,8 +309,9 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
});
|
||||
}
|
||||
Context::Thread(_) => (), // do nothing (yet?)
|
||||
Context::None => (), // do nothing (yet?)
|
||||
Context::Thread(_) => (), // do nothing (yet?)
|
||||
Context::ThreadResult(_) => (), // do nothing (yet?)
|
||||
Context::None => (), // do nothing (yet?)
|
||||
};
|
||||
}
|
||||
Msg::PreviousPage => {
|
||||
@@ -317,8 +333,9 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
});
|
||||
}
|
||||
|
||||
Context::Thread(_) => (), // do nothing (yet?)
|
||||
Context::None => (), // do nothing (yet?)
|
||||
Context::Thread(_) => (), // do nothing (yet?)
|
||||
Context::ThreadResult(_) => (), // do nothing (yet?)
|
||||
Context::None => (), // do nothing (yet?)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -372,6 +389,25 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
pager: data.search.page_info,
|
||||
};
|
||||
}
|
||||
|
||||
Msg::ShowThreadRequest { thread_id } => {
|
||||
orders.skip().perform_cmd(async move {
|
||||
Msg::ShowThreadResult(
|
||||
send_graphql(graphql::ShowThreadQuery::build_query(
|
||||
graphql::show_thread_query::Variables { thread_id },
|
||||
))
|
||||
.await,
|
||||
)
|
||||
});
|
||||
}
|
||||
Msg::ShowThreadResult(Ok(graphql_client::Response {
|
||||
data: Some(data), ..
|
||||
})) => {
|
||||
model.context = Context::ThreadResult(data.thread);
|
||||
}
|
||||
Msg::ShowThreadResult(bad) => {
|
||||
error!("show_thread_query error: {bad:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -923,7 +959,108 @@ fn view_search_pager_legacy(start: usize, count: usize, total: usize) -> Node<Ms
|
||||
]
|
||||
}
|
||||
|
||||
fn view_thread(thread_set: &ThreadSet) -> Node<Msg> {
|
||||
trait Email {
|
||||
fn name(&self) -> &Option<String>;
|
||||
fn addr(&self) -> &Option<String>;
|
||||
}
|
||||
|
||||
impl<T: Email> Email for &'_ T {
|
||||
fn name(&self) -> &Option<String> {
|
||||
return (*self).name();
|
||||
}
|
||||
fn addr(&self) -> &Option<String> {
|
||||
return (*self).addr();
|
||||
}
|
||||
}
|
||||
|
||||
impl Email for ShowThreadQueryThreadMessagesCc {
|
||||
fn name(&self) -> &Option<String> {
|
||||
return &self.name;
|
||||
}
|
||||
fn addr(&self) -> &Option<String> {
|
||||
return &self.addr;
|
||||
}
|
||||
}
|
||||
impl Email for ShowThreadQueryThreadMessagesFrom {
|
||||
fn name(&self) -> &Option<String> {
|
||||
return &self.name;
|
||||
}
|
||||
fn addr(&self) -> &Option<String> {
|
||||
return &self.addr;
|
||||
}
|
||||
}
|
||||
impl Email for ShowThreadQueryThreadMessagesTo {
|
||||
fn name(&self) -> &Option<String> {
|
||||
return &self.name;
|
||||
}
|
||||
fn addr(&self) -> &Option<String> {
|
||||
return &self.addr;
|
||||
}
|
||||
}
|
||||
|
||||
fn view_addresses<E: Email>(addrs: &[E]) -> Vec<Node<Msg>> {
|
||||
addrs
|
||||
.into_iter()
|
||||
.map(|address| {
|
||||
span![
|
||||
C!["tag", "is-black"],
|
||||
address.addr().as_ref().map(|a| attrs! {At::Title=>a}),
|
||||
address
|
||||
.name()
|
||||
.as_ref()
|
||||
.unwrap_or(address.addr().as_ref().unwrap_or(&"(UNKNOWN)".to_string()))
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn view_thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
|
||||
// TODO(wathiede): show per-message subject if it changes significantly from top-level subject
|
||||
set_title(&thread.subject);
|
||||
let messages = thread.messages.iter().map(|msg| {
|
||||
div![
|
||||
C!["message"],
|
||||
/* TODO(wathiede): collect all the tags and show them here. */
|
||||
/* TODO(wathiede): collect all the attachments from all the subparts */
|
||||
msg.from
|
||||
.as_ref()
|
||||
.map(|from| div![C!["header"], "From: ", view_addresses(&[from])]),
|
||||
msg.timestamp
|
||||
.map(|ts| div![C!["header"], "Date: ", human_age(ts)]),
|
||||
IF!(!msg.to.is_empty() => div![C!["header"], "To: ", view_addresses(&msg.to)]),
|
||||
IF!(!msg.cc.is_empty() => div![C!["header"], "CC: ", view_addresses(&msg.cc)]),
|
||||
/*
|
||||
div![
|
||||
C!["body"],
|
||||
match &message.body {
|
||||
Some(body) => view_body(body.as_slice()),
|
||||
None => div!["<no body>"],
|
||||
},
|
||||
],
|
||||
*/
|
||||
]
|
||||
});
|
||||
div![
|
||||
C!["container"],
|
||||
h1![C!["title"], &thread.subject],
|
||||
messages,
|
||||
/* TODO(wathiede): plumb in orignal id
|
||||
a![
|
||||
attrs! {At::Href=>api::original(&thread_node.0.as_ref().expect("message missing").id)},
|
||||
"Original"
|
||||
],
|
||||
*/
|
||||
/*
|
||||
div![
|
||||
C!["debug"],
|
||||
"Add zippy for debug dump",
|
||||
view_debug_thread_set(thread_set)
|
||||
] /* pre![format!("Thread: {:#?}", thread_set).replace(" ", " ")] */
|
||||
*/
|
||||
]
|
||||
}
|
||||
|
||||
fn view_thread_legacy(thread_set: &ThreadSet) -> Node<Msg> {
|
||||
assert_eq!(thread_set.0.len(), 1);
|
||||
let thread = &thread_set.0[0];
|
||||
assert_eq!(thread.0.len(), 1);
|
||||
@@ -1058,7 +1195,8 @@ 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(thread_set),
|
||||
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,
|
||||
@@ -1097,7 +1235,8 @@ fn view_desktop(model: &Model) -> Node<Msg> {
|
||||
fn view_mobile(model: &Model) -> Node<Msg> {
|
||||
let content = match &model.context {
|
||||
Context::None => div![h1!["Loading"]],
|
||||
Context::Thread(thread_set) => view_thread(thread_set),
|
||||
Context::Thread(thread_set) => view_thread_legacy(thread_set),
|
||||
Context::ThreadResult(thread) => view_thread(thread),
|
||||
Context::Search(search_results) => {
|
||||
view_mobile_search_results_legacy(&model.query, search_results)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user