WIP: Simple frontend and backend to search notmuch mail.
This commit is contained in:
161
web/src/lib.rs
Normal file
161
web/src/lib.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
// (Lines like the one below ignore selected Clippy rules
|
||||
// - it's useful when you want to check your code with `cargo make verify`
|
||||
// but some rules are too "annoying" or are not applicable for your case.)
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
|
||||
use log::{error, info, Level};
|
||||
use seed::{prelude::*, *};
|
||||
use serde::Deserialize;
|
||||
use web_sys::HtmlInputElement;
|
||||
|
||||
// ------ ------
|
||||
// Init
|
||||
// ------ ------
|
||||
|
||||
// `init` describes what should happen when your app started.
|
||||
fn init(_: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
orders
|
||||
.skip()
|
||||
.perform_cmd(async { Msg::SearchResult(search_request("is:unread").await) });
|
||||
Model {
|
||||
search_results: None,
|
||||
}
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
// Model
|
||||
// ------ ------
|
||||
|
||||
// `Model` describes our app state.
|
||||
struct Model {
|
||||
search_results: Option<Vec<SearchResult>>,
|
||||
}
|
||||
|
||||
/*
|
||||
[
|
||||
{
|
||||
"thread": "0000000000022fa5",
|
||||
"timestamp": 1634947104,
|
||||
"date_relative": "Yest. 16:58",
|
||||
"matched": 1,
|
||||
"total": 1,
|
||||
"authors": "Blue Cross Blue Shield Claims Administrator",
|
||||
"subject": "BCBS Settlement Claim Received",
|
||||
"query": [
|
||||
"id:20211022235824.232afe38e3e07950@bcbssettlement.com",
|
||||
null
|
||||
],
|
||||
"tags": [
|
||||
"inbox"
|
||||
]
|
||||
}
|
||||
]
|
||||
*/
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SearchResult {
|
||||
thread: String,
|
||||
timestamp: u64,
|
||||
date_relative: String,
|
||||
matched: isize,
|
||||
total: isize,
|
||||
authors: String,
|
||||
subject: String,
|
||||
// TODO(wathiede): what are these?
|
||||
//query:Vec<_>,
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
// Update
|
||||
// ------ ------
|
||||
|
||||
// (Remove the line below once any of your `Msg` variants doesn't implement `Copy`.)
|
||||
// `Msg` describes the different events you can modify state with.
|
||||
enum Msg {
|
||||
SearchRequest(String),
|
||||
// TODO(wathiede): replace String with serde json decoded struct
|
||||
SearchResult(fetch::Result<Vec<SearchResult>>),
|
||||
}
|
||||
|
||||
// `update` describes how to handle each `Msg`.
|
||||
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::SearchRequest(query) => {
|
||||
orders
|
||||
.skip()
|
||||
.perform_cmd(async move { Msg::SearchResult(search_request(&query).await) });
|
||||
}
|
||||
|
||||
Msg::SearchResult(Ok(response_data)) => {
|
||||
info!("fetch ok {:?}", response_data);
|
||||
model.search_results = Some(response_data);
|
||||
}
|
||||
|
||||
Msg::SearchResult(Err(fetch_error)) => {
|
||||
error!("fetch failed {:?}", fetch_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn search_request(query: &str) -> fetch::Result<Vec<SearchResult>> {
|
||||
Request::new(get_search_request_url(query))
|
||||
.method(Method::Get)
|
||||
.fetch()
|
||||
.await?
|
||||
.check_status()?
|
||||
.json()
|
||||
.await
|
||||
}
|
||||
|
||||
fn get_search_request_url(query: &str) -> String {
|
||||
format!("http://nixos-07:9345/search/{}", query)
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
// View
|
||||
// ------ ------
|
||||
|
||||
// `view` describes what to display.
|
||||
fn view(model: &Model) -> Node<Msg> {
|
||||
let results = if let Some(res) = &model.search_results {
|
||||
div![res.iter().map(|r| p![
|
||||
h3![&r.subject, " ", small![&r.date_relative]],
|
||||
div![span![&r.authors]]
|
||||
])]
|
||||
} else {
|
||||
div![]
|
||||
};
|
||||
div![
|
||||
"Do Something: ",
|
||||
button![
|
||||
"Unread",
|
||||
ev(Ev::Click, |_| Msg::SearchRequest("is:unread".to_string())),
|
||||
],
|
||||
input![
|
||||
attrs! {
|
||||
At::Placeholder => "Search";
|
||||
At::AutoFocus => true.as_at_value();
|
||||
},
|
||||
input_ev(Ev::Input, Msg::SearchRequest),
|
||||
],
|
||||
results,
|
||||
]
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
// Start
|
||||
// ------ ------
|
||||
|
||||
// (This function is invoked by `init` function in `index.html`.)
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn start() {
|
||||
// This provides better error messages in debug mode.
|
||||
// It's disabled in release mode so it doesn't bloat up the file size.
|
||||
#[cfg(debug_assertions)]
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let lvl = Level::Info;
|
||||
console_log::init_with_level(lvl).expect("failed to initialize console logging");
|
||||
// Mount the `app` to the element with the `id` "app".
|
||||
App::start("app", init, update, view);
|
||||
}
|
||||
Reference in New Issue
Block a user