Add refresh button and mobile view.
This commit is contained in:
parent
c33f901f48
commit
0b4cfadf88
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -983,6 +983,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
"wasm-timer",
|
"wasm-timer",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -18,6 +18,10 @@ fn hello() -> &'static str {
|
|||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/refresh")]
|
||||||
|
async fn refresh(nm: &State<Notmuch>) -> Result<Json<String>, Debug<NotmuchError>> {
|
||||||
|
Ok(Json(String::from_utf8_lossy(&nm.new()?).to_string()))
|
||||||
|
}
|
||||||
#[get("/search")]
|
#[get("/search")]
|
||||||
async fn search_all(nm: &State<Notmuch>) -> Result<Json<SearchSummary>, Debug<NotmuchError>> {
|
async fn search_all(nm: &State<Notmuch>) -> Result<Json<SearchSummary>, Debug<NotmuchError>> {
|
||||||
search(nm, "*").await
|
search(nm, "*").await
|
||||||
@ -123,6 +127,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
original_part,
|
original_part,
|
||||||
original,
|
original,
|
||||||
hello,
|
hello,
|
||||||
|
refresh,
|
||||||
search_all,
|
search_all,
|
||||||
search,
|
search,
|
||||||
show_pretty,
|
show_pretty,
|
||||||
|
|||||||
@ -28,3 +28,10 @@ wasm-timer = "0.2.5"
|
|||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
[package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = ['-Os']
|
wasm-opt = ['-Os']
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3.58"
|
||||||
|
features = [
|
||||||
|
"MediaQueryList",
|
||||||
|
"Window"
|
||||||
|
]
|
||||||
|
|||||||
110
web/src/lib.rs
110
web/src/lib.rs
@ -74,6 +74,7 @@ struct Model {
|
|||||||
// `Msg` describes the different events you can modify state with.
|
// `Msg` describes the different events you can modify state with.
|
||||||
enum Msg {
|
enum Msg {
|
||||||
Noop,
|
Noop,
|
||||||
|
Refresh,
|
||||||
SearchRequest(String),
|
SearchRequest(String),
|
||||||
SearchResult(fetch::Result<SearchSummary>),
|
SearchResult(fetch::Result<SearchSummary>),
|
||||||
ShowRequest(String),
|
ShowRequest(String),
|
||||||
@ -86,6 +87,11 @@ enum Msg {
|
|||||||
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
match msg {
|
match msg {
|
||||||
Msg::Noop => {}
|
Msg::Noop => {}
|
||||||
|
Msg::Refresh => {
|
||||||
|
orders.skip().perform_cmd(async move {
|
||||||
|
refresh_request().await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Msg::SearchRequest(query) => {
|
Msg::SearchRequest(query) => {
|
||||||
model.query = query.clone();
|
model.query = query.clone();
|
||||||
@ -147,20 +153,35 @@ async fn search_request(query: &str) -> fetch::Result<SearchSummary> {
|
|||||||
|
|
||||||
mod api {
|
mod api {
|
||||||
const BASE_URL: &str = "/api";
|
const BASE_URL: &str = "/api";
|
||||||
|
pub fn refresh() -> String {
|
||||||
|
format!("{BASE_URL}/refresh")
|
||||||
|
}
|
||||||
pub fn search(query: &str) -> String {
|
pub fn search(query: &str) -> String {
|
||||||
format!("{}/search/{}", BASE_URL, query)
|
format!("{BASE_URL}/search/{query}")
|
||||||
}
|
}
|
||||||
pub fn show(tid: &str) -> String {
|
pub fn show(tid: &str) -> String {
|
||||||
format!("{}/show/{}", BASE_URL, tid)
|
format!("{BASE_URL}/show/{tid}")
|
||||||
}
|
}
|
||||||
pub fn show_pretty(tid: &str) -> String {
|
pub fn show_pretty(tid: &str) -> String {
|
||||||
format!("{}/show/{}/pretty", BASE_URL, tid)
|
format!("{BASE_URL}/show/{tid}/pretty")
|
||||||
}
|
}
|
||||||
pub fn original(message_id: &str) -> String {
|
pub fn original(message_id: &str) -> String {
|
||||||
format!("{}/original/{}", BASE_URL, message_id)
|
format!("{BASE_URL}/original/{message_id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn refresh_request() -> fetch::Result<()> {
|
||||||
|
let t = Request::new(api::refresh())
|
||||||
|
.method(Method::Get)
|
||||||
|
.fetch()
|
||||||
|
.await?
|
||||||
|
.check_status()?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
info!("refresh {t}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn show_request(tid: &str) -> fetch::Result<ThreadSet> {
|
async fn show_request(tid: &str) -> fetch::Result<ThreadSet> {
|
||||||
let b = Request::new(api::show(tid))
|
let b = Request::new(api::show(tid))
|
||||||
.method(Method::Get)
|
.method(Method::Get)
|
||||||
@ -346,6 +367,39 @@ fn pretty_authors(authors: &str) -> impl Iterator<Item = Node<Msg>> + '_ {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn view_mobile_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
|
||||||
|
if query.is_empty() {
|
||||||
|
set_title("all mail");
|
||||||
|
} else {
|
||||||
|
set_title(query);
|
||||||
|
}
|
||||||
|
let rows = search_results.0.iter().map(|r| {
|
||||||
|
/*
|
||||||
|
let tid = r.thread.clone();
|
||||||
|
tr![
|
||||||
|
td![
|
||||||
|
C!["from"],
|
||||||
|
pretty_authors(&r.authors),
|
||||||
|
IF!(r.total>1 => small![" ", r.total.to_string()]),
|
||||||
|
],
|
||||||
|
td![C!["subject"], tags_chiclet(&r.tags), " ", &r.subject],
|
||||||
|
td![C!["date"], &r.date_relative],
|
||||||
|
ev(Ev::Click, move |_| Msg::ShowPrettyRequest(tid)),
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
div![
|
||||||
|
p![C!["subject"], &r.subject],
|
||||||
|
div![
|
||||||
|
span![C!["from"], pretty_authors(&r.authors)],
|
||||||
|
span![C!["tags"], tags_chiclet(&r.tags)],
|
||||||
|
],
|
||||||
|
span![C!["date"], &r.date_relative],
|
||||||
|
hr![],
|
||||||
|
]
|
||||||
|
});
|
||||||
|
div![h1!["Search results"], rows]
|
||||||
|
}
|
||||||
|
|
||||||
fn view_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
|
fn view_search_results(query: &str, search_results: &SearchSummary) -> Node<Msg> {
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
set_title("all mail");
|
set_title("all mail");
|
||||||
@ -439,7 +493,12 @@ fn view_header(query: &str) -> Node<Msg> {
|
|||||||
div![
|
div![
|
||||||
C!["navbar-start"],
|
C!["navbar-start"],
|
||||||
a![
|
a![
|
||||||
C!["navbar-item", "button",],
|
C!["navbar-item", "button"],
|
||||||
|
span![i![C!["fa-solid", "fa-arrow-rotate-right"]]],
|
||||||
|
ev(Ev::Click, |_| Msg::Refresh),
|
||||||
|
],
|
||||||
|
a![
|
||||||
|
C!["navbar-item", "button"],
|
||||||
"Unread",
|
"Unread",
|
||||||
ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())),
|
ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())),
|
||||||
],
|
],
|
||||||
@ -477,10 +536,7 @@ fn view_footer(render_time_ms: u128) -> Node<Msg> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// `view` describes what to display.
|
fn view_desktop(model: &Model) -> Node<Msg> {
|
||||||
fn view(model: &Model) -> Node<Msg> {
|
|
||||||
let start = Instant::now();
|
|
||||||
info!("view called");
|
|
||||||
let content = match &model.context {
|
let content = match &model.context {
|
||||||
Context::None => div![h1!["Loading"]],
|
Context::None => div![h1!["Loading"]],
|
||||||
Context::Thread(thread_set) => view_thread(thread_set),
|
Context::Thread(thread_set) => view_thread(thread_set),
|
||||||
@ -488,11 +544,39 @@ fn view(model: &Model) -> Node<Msg> {
|
|||||||
};
|
};
|
||||||
div![
|
div![
|
||||||
view_header(&model.query),
|
view_header(&model.query),
|
||||||
section![
|
section![C!["section"], div![C!["container"], content],]
|
||||||
C!["section"],
|
|
||||||
div![C!["container"], content],
|
|
||||||
view_footer(start.elapsed().as_millis())
|
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Search(search_results) => view_mobile_search_results(&model.query, search_results),
|
||||||
|
};
|
||||||
|
div![
|
||||||
|
view_header(&model.query),
|
||||||
|
section![C!["section"], div![C!["container"], content],]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// `view` describes what to display.
|
||||||
|
fn view(model: &Model) -> Node<Msg> {
|
||||||
|
let is_mobile = seed::window()
|
||||||
|
.match_media("(max-width: 768px)")
|
||||||
|
.expect("failed media query")
|
||||||
|
.map(|mql| mql.matches())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let start = Instant::now();
|
||||||
|
info!("view called");
|
||||||
|
div![
|
||||||
|
if is_mobile {
|
||||||
|
view_mobile(model)
|
||||||
|
} else {
|
||||||
|
view_desktop(model)
|
||||||
|
},
|
||||||
|
view_footer(start.elapsed().as_millis())
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user