Inline CSS on message view, and do error handling on refresh.
This commit is contained in:
parent
0b4cfadf88
commit
3a47385be1
691
Cargo.lock
generated
691
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ serde_json = "1.0.87"
|
||||
thiserror = "1.0.37"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
log = "0.4.17"
|
||||
tokio = "1.26.0"
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
version = "0.4.11"
|
||||
|
||||
@ -25,6 +25,7 @@ notmuch = {path = "../notmuch"}
|
||||
itertools = "0.10.5"
|
||||
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
|
||||
wasm-timer = "0.2.5"
|
||||
css-inline = "0.8.5"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ['-Os']
|
||||
|
||||
@ -45,6 +45,25 @@ iframe {
|
||||
.tag {
|
||||
margin-right: 2px;
|
||||
}
|
||||
.debug ul {
|
||||
padding-left: 2em;
|
||||
}
|
||||
.debug li {
|
||||
}
|
||||
.loading {
|
||||
animation-name: spin;
|
||||
animation-duration: 1000ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform:rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform:rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
@ -48,6 +48,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
Model {
|
||||
context: Context::None,
|
||||
query,
|
||||
refreshing_state: RefreshingState::None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +65,14 @@ enum Context {
|
||||
struct Model {
|
||||
query: String,
|
||||
context: Context,
|
||||
refreshing_state: RefreshingState,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum RefreshingState {
|
||||
None,
|
||||
Loading,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
// ------ ------
|
||||
@ -74,7 +83,8 @@ struct Model {
|
||||
// `Msg` describes the different events you can modify state with.
|
||||
enum Msg {
|
||||
Noop,
|
||||
Refresh,
|
||||
RefreshStart,
|
||||
RefreshDone(Option<FetchError>),
|
||||
SearchRequest(String),
|
||||
SearchResult(fetch::Result<SearchSummary>),
|
||||
ShowRequest(String),
|
||||
@ -87,10 +97,16 @@ enum Msg {
|
||||
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::Noop => {}
|
||||
Msg::Refresh => {
|
||||
orders.skip().perform_cmd(async move {
|
||||
refresh_request().await;
|
||||
});
|
||||
Msg::RefreshStart => {
|
||||
model.refreshing_state = RefreshingState::Loading;
|
||||
orders.perform_cmd(async move { Msg::RefreshDone(refresh_request().await.err()) });
|
||||
}
|
||||
Msg::RefreshDone(err) => {
|
||||
model.refreshing_state = if let Some(err) = err {
|
||||
RefreshingState::Error(format!("{:?}", err))
|
||||
} else {
|
||||
RefreshingState::None
|
||||
};
|
||||
}
|
||||
|
||||
Msg::SearchRequest(query) => {
|
||||
@ -264,7 +280,9 @@ fn view_part(part: &Part) -> Node<Msg> {
|
||||
"text/plain" => view_text_plain(&part.content),
|
||||
"text/html" => {
|
||||
if let Some(Content::String(html)) = &part.content {
|
||||
return div![C!["view-part-text-html"], div!["TEST"], raw![&html]];
|
||||
let inlined = css_inline::inline(html).expect("failed to inline CSS");
|
||||
|
||||
return div![C!["view-part-text-html"], div!["TEST"], raw![&inlined]];
|
||||
} else {
|
||||
div![
|
||||
C!["error"],
|
||||
@ -282,7 +300,12 @@ fn view_part(part: &Part) -> Node<Msg> {
|
||||
for part in parts.iter().rev() {
|
||||
if part.content_type == "text/html" {
|
||||
if let Some(Content::String(html)) = &part.content {
|
||||
return div![Node::from_html(None, &html)];
|
||||
let inliner = css_inline::CSSInliner::options()
|
||||
.load_remote_stylesheets(false)
|
||||
.remove_style_tags(true)
|
||||
.build();
|
||||
let inlined = inliner.inline(html).expect("failed to inline CSS");
|
||||
return div![Node::from_html(None, &inlined)];
|
||||
}
|
||||
}
|
||||
if part.content_type == "text/plain" {
|
||||
@ -451,7 +474,8 @@ fn view_thread(thread_set: &ThreadSet) -> Node<Msg> {
|
||||
"Original"
|
||||
],
|
||||
view_message(&thread_node),
|
||||
pre![
|
||||
div![
|
||||
C!["debug"],
|
||||
"Add zippy for debug dump",
|
||||
view_debug_thread_set(thread_set)
|
||||
] /* pre![format!("Thread: {:#?}", thread_set).replace(" ", " ")] */
|
||||
@ -485,7 +509,14 @@ fn view_debug_thread_node(thread_node: &ThreadNode) -> Node<Msg> {
|
||||
]
|
||||
}
|
||||
|
||||
fn view_header(query: &str) -> Node<Msg> {
|
||||
fn view_header(query: &str, refresh_request: &RefreshingState) -> Node<Msg> {
|
||||
let is_loading = refresh_request == &RefreshingState::Loading;
|
||||
let is_error = if let RefreshingState::Error(err) = refresh_request {
|
||||
error!("Failed to refresh: {err:?}");
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let query = query.to_string();
|
||||
nav![
|
||||
C!["navbar"],
|
||||
@ -493,9 +524,14 @@ fn view_header(query: &str) -> Node<Msg> {
|
||||
div![
|
||||
C!["navbar-start"],
|
||||
a![
|
||||
C!["navbar-item", "button"],
|
||||
span![i![C!["fa-solid", "fa-arrow-rotate-right"]]],
|
||||
ev(Ev::Click, |_| Msg::Refresh),
|
||||
C!["navbar-item", "button", IF![is_error => "is-danger"]],
|
||||
span![i![C![
|
||||
"fa-solid",
|
||||
"fa-arrow-rotate-right",
|
||||
"refresh",
|
||||
IF![is_loading => "loading"],
|
||||
]]],
|
||||
ev(Ev::Click, |_| Msg::RefreshStart),
|
||||
],
|
||||
a![
|
||||
C!["navbar-item", "button"],
|
||||
@ -543,7 +579,7 @@ fn view_desktop(model: &Model) -> Node<Msg> {
|
||||
Context::Search(search_results) => view_search_results(&model.query, search_results),
|
||||
};
|
||||
div![
|
||||
view_header(&model.query),
|
||||
view_header(&model.query, &model.refreshing_state),
|
||||
section![C!["section"], div![C!["container"], content],]
|
||||
]
|
||||
}
|
||||
@ -555,13 +591,14 @@ fn view_mobile(model: &Model) -> Node<Msg> {
|
||||
Context::Search(search_results) => view_mobile_search_results(&model.query, search_results),
|
||||
};
|
||||
div![
|
||||
view_header(&model.query),
|
||||
view_header(&model.query, &model.refreshing_state),
|
||||
section![C!["section"], div![C!["container"], content],]
|
||||
]
|
||||
}
|
||||
|
||||
// `view` describes what to display.
|
||||
fn view(model: &Model) -> Node<Msg> {
|
||||
info!("refreshing {:?}", model.refreshing_state);
|
||||
let is_mobile = seed::window()
|
||||
.match_media("(max-width: 768px)")
|
||||
.expect("failed media query")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user