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"
|
thiserror = "1.0.37"
|
||||||
serde = { version = "1.0.147", features = ["derive"] }
|
serde = { version = "1.0.147", features = ["derive"] }
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
tokio = "1.26.0"
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
|||||||
@ -25,6 +25,7 @@ notmuch = {path = "../notmuch"}
|
|||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
|
serde_json = { version = "1.0.93", features = ["unbounded_depth"] }
|
||||||
wasm-timer = "0.2.5"
|
wasm-timer = "0.2.5"
|
||||||
|
css-inline = "0.8.5"
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.release]
|
[package.metadata.wasm-pack.profile.release]
|
||||||
wasm-opt = ['-Os']
|
wasm-opt = ['-Os']
|
||||||
|
|||||||
@ -45,6 +45,25 @@ iframe {
|
|||||||
.tag {
|
.tag {
|
||||||
margin-right: 2px;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -48,6 +48,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
Model {
|
Model {
|
||||||
context: Context::None,
|
context: Context::None,
|
||||||
query,
|
query,
|
||||||
|
refreshing_state: RefreshingState::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +65,14 @@ enum Context {
|
|||||||
struct Model {
|
struct Model {
|
||||||
query: String,
|
query: String,
|
||||||
context: Context,
|
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.
|
// `Msg` describes the different events you can modify state with.
|
||||||
enum Msg {
|
enum Msg {
|
||||||
Noop,
|
Noop,
|
||||||
Refresh,
|
RefreshStart,
|
||||||
|
RefreshDone(Option<FetchError>),
|
||||||
SearchRequest(String),
|
SearchRequest(String),
|
||||||
SearchResult(fetch::Result<SearchSummary>),
|
SearchResult(fetch::Result<SearchSummary>),
|
||||||
ShowRequest(String),
|
ShowRequest(String),
|
||||||
@ -87,10 +97,16 @@ 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 => {
|
Msg::RefreshStart => {
|
||||||
orders.skip().perform_cmd(async move {
|
model.refreshing_state = RefreshingState::Loading;
|
||||||
refresh_request().await;
|
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) => {
|
Msg::SearchRequest(query) => {
|
||||||
@ -264,7 +280,9 @@ fn view_part(part: &Part) -> Node<Msg> {
|
|||||||
"text/plain" => view_text_plain(&part.content),
|
"text/plain" => view_text_plain(&part.content),
|
||||||
"text/html" => {
|
"text/html" => {
|
||||||
if let Some(Content::String(html)) = &part.content {
|
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 {
|
} else {
|
||||||
div![
|
div![
|
||||||
C!["error"],
|
C!["error"],
|
||||||
@ -282,7 +300,12 @@ fn view_part(part: &Part) -> Node<Msg> {
|
|||||||
for part in parts.iter().rev() {
|
for part in parts.iter().rev() {
|
||||||
if part.content_type == "text/html" {
|
if part.content_type == "text/html" {
|
||||||
if let Some(Content::String(html)) = &part.content {
|
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" {
|
if part.content_type == "text/plain" {
|
||||||
@ -451,7 +474,8 @@ fn view_thread(thread_set: &ThreadSet) -> Node<Msg> {
|
|||||||
"Original"
|
"Original"
|
||||||
],
|
],
|
||||||
view_message(&thread_node),
|
view_message(&thread_node),
|
||||||
pre![
|
div![
|
||||||
|
C!["debug"],
|
||||||
"Add zippy for debug dump",
|
"Add zippy for debug dump",
|
||||||
view_debug_thread_set(thread_set)
|
view_debug_thread_set(thread_set)
|
||||||
] /* pre![format!("Thread: {:#?}", thread_set).replace(" ", " ")] */
|
] /* 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();
|
let query = query.to_string();
|
||||||
nav![
|
nav![
|
||||||
C!["navbar"],
|
C!["navbar"],
|
||||||
@ -493,9 +524,14 @@ 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", IF![is_error => "is-danger"]],
|
||||||
span![i![C!["fa-solid", "fa-arrow-rotate-right"]]],
|
span![i![C![
|
||||||
ev(Ev::Click, |_| Msg::Refresh),
|
"fa-solid",
|
||||||
|
"fa-arrow-rotate-right",
|
||||||
|
"refresh",
|
||||||
|
IF![is_loading => "loading"],
|
||||||
|
]]],
|
||||||
|
ev(Ev::Click, |_| Msg::RefreshStart),
|
||||||
],
|
],
|
||||||
a![
|
a![
|
||||||
C!["navbar-item", "button"],
|
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),
|
Context::Search(search_results) => view_search_results(&model.query, search_results),
|
||||||
};
|
};
|
||||||
div![
|
div![
|
||||||
view_header(&model.query),
|
view_header(&model.query, &model.refreshing_state),
|
||||||
section![C!["section"], div![C!["container"], content],]
|
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),
|
Context::Search(search_results) => view_mobile_search_results(&model.query, search_results),
|
||||||
};
|
};
|
||||||
div![
|
div![
|
||||||
view_header(&model.query),
|
view_header(&model.query, &model.refreshing_state),
|
||||||
section![C!["section"], div![C!["container"], content],]
|
section![C!["section"], div![C!["container"], content],]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// `view` describes what to display.
|
// `view` describes what to display.
|
||||||
fn view(model: &Model) -> Node<Msg> {
|
fn view(model: &Model) -> Node<Msg> {
|
||||||
|
info!("refreshing {:?}", model.refreshing_state);
|
||||||
let is_mobile = seed::window()
|
let is_mobile = seed::window()
|
||||||
.match_media("(max-width: 768px)")
|
.match_media("(max-width: 768px)")
|
||||||
.expect("failed media query")
|
.expect("failed media query")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user