mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-28 09:02:37 -05:00
Compare commits
6 Commits
revert-538
...
v0.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
322041917d | ||
|
|
a2eaf9b3ee | ||
|
|
4032bfc210 | ||
|
|
4ff08f042b | ||
|
|
ce4b0ecbe1 | ||
|
|
6c31d09eb2 |
@@ -97,10 +97,10 @@ pub fn Counter(cx: Scope) -> impl IntoView {
|
||||
|_| get_server_count(),
|
||||
);
|
||||
|
||||
let value = move || counter.read().map(|count| count.unwrap_or(0)).unwrap_or(0);
|
||||
let value = move || counter.read(cx).map(|count| count.unwrap_or(0)).unwrap_or(0);
|
||||
let error_msg = move || {
|
||||
counter
|
||||
.read()
|
||||
.read(cx)
|
||||
.map(|res| match res {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e),
|
||||
@@ -143,7 +143,7 @@ pub fn FormCounter(cx: Scope) -> impl IntoView {
|
||||
let value = move || {
|
||||
log::debug!("FormCounter looking for value");
|
||||
counter
|
||||
.read()
|
||||
.read(cx)
|
||||
.map(|n| n.ok())
|
||||
.flatten()
|
||||
.map(|n| n)
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn fetch_example(cx: Scope) -> impl IntoView {
|
||||
// and by using the ErrorBoundary fallback to catch Err(_)
|
||||
// so we'll just implement our happy path and let the framework handle the rest
|
||||
let cats_view = move || {
|
||||
cats.with(|data| {
|
||||
cats.with(cx, |data| {
|
||||
data.iter()
|
||||
.flatten()
|
||||
.map(|cat| view! { cx, <img src={cat}/> })
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
let (pending, set_pending) = create_signal(cx, false);
|
||||
|
||||
let hide_more_link =
|
||||
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
move || pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
|
||||
view! {
|
||||
cx,
|
||||
@@ -82,7 +82,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
fallback=move || view! { cx, <p>"Loading..."</p> }
|
||||
set_pending=set_pending.into()
|
||||
>
|
||||
{move || match stories.read() {
|
||||
{move || match stories.read(cx) {
|
||||
None => None,
|
||||
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
|
||||
Some(Some(stories)) => {
|
||||
|
||||
@@ -17,13 +17,13 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
<Meta name="description" content=meta_description/>
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || story.read().map(|story| match story {
|
||||
{move || story.read(cx).map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn User(cx: Scope) -> impl IntoView {
|
||||
view! { cx,
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || user.read().map(|user| match user {
|
||||
{move || user.read(cx).map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
let (pending, set_pending) = create_signal(cx, false);
|
||||
|
||||
let hide_more_link =
|
||||
move || pending() || stories.read().unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
move || pending() || stories.read(cx).unwrap_or(None).unwrap_or_default().len() < 28;
|
||||
|
||||
view! {
|
||||
cx,
|
||||
@@ -82,7 +82,7 @@ pub fn Stories(cx: Scope) -> impl IntoView {
|
||||
fallback=move || view! { cx, <p>"Loading..."</p> }
|
||||
set_pending=set_pending.into()
|
||||
>
|
||||
{move || match stories.read() {
|
||||
{move || match stories.read(cx) {
|
||||
None => None,
|
||||
Some(None) => Some(view! { cx, <p>"Error loading stories."</p> }.into_any()),
|
||||
Some(Some(stories)) => {
|
||||
|
||||
@@ -17,13 +17,13 @@ pub fn Story(cx: Scope) -> impl IntoView {
|
||||
}
|
||||
},
|
||||
);
|
||||
let meta_description = move || story.read().and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
let meta_description = move || story.read(cx).and_then(|story| story.map(|story| story.title)).unwrap_or_else(|| "Loading story...".to_string());
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
<Meta name="description" content=meta_description/>
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || story.read().map(|story| match story {
|
||||
{move || story.read(cx).map(|story| match story {
|
||||
None => view! { cx, <div class="item-view">"Error loading this story."</div> },
|
||||
Some(story) => view! { cx,
|
||||
<div class="item-view">
|
||||
|
||||
@@ -19,7 +19,7 @@ pub fn User(cx: Scope) -> impl IntoView {
|
||||
view! { cx,
|
||||
<div class="user-view">
|
||||
<Suspense fallback=|| view! { cx, "Loading..." }>
|
||||
{move || user.read().map(|user| match user {
|
||||
{move || user.read(cx).map(|user| match user {
|
||||
None => view! { cx, <h1>"User not found."</h1> }.into_any(),
|
||||
Some(user) => view! { cx,
|
||||
<div>
|
||||
|
||||
@@ -71,9 +71,10 @@ pub fn ContactList(cx: Scope) -> impl IntoView {
|
||||
});
|
||||
|
||||
let location = use_location(cx);
|
||||
let contacts = create_resource(cx, move || location.search.get(), get_contacts);
|
||||
let contacts =
|
||||
create_resource(cx, move || location.search.get(), get_contacts);
|
||||
let contacts = move || {
|
||||
contacts.read().map(|contacts| {
|
||||
contacts.read(cx).map(|contacts| {
|
||||
// this data doesn't change frequently so we can use .map().collect() instead of a keyed <For/>
|
||||
contacts
|
||||
.into_iter()
|
||||
@@ -126,12 +127,15 @@ pub fn Contact(cx: Scope) -> impl IntoView {
|
||||
get_contact,
|
||||
);
|
||||
|
||||
let contact_display = move || match contact.read() {
|
||||
let contact_display = move || match contact.read(cx) {
|
||||
// None => loading, but will be caught by Suspense fallback
|
||||
// I'm only doing this explicitly for the example
|
||||
None => None,
|
||||
// Some(None) => has loaded and found no contact
|
||||
Some(None) => Some(view! { cx, <p>"No contact with this ID was found."</p> }.into_any()),
|
||||
Some(None) => Some(
|
||||
view! { cx, <p>"No contact with this ID was found."</p> }
|
||||
.into_any(),
|
||||
),
|
||||
// Some(Some) => has loaded and found a contact
|
||||
Some(Some(contact)) => Some(
|
||||
view! { cx,
|
||||
|
||||
@@ -39,7 +39,7 @@ fn HomePage(cx: Scope) -> impl IntoView {
|
||||
let posts =
|
||||
create_resource(cx, || (), |_| async { list_post_metadata().await });
|
||||
let posts_view = move || {
|
||||
posts.with(|posts| posts
|
||||
posts.with(cx, |posts| posts
|
||||
.clone()
|
||||
.map(|posts| {
|
||||
posts.iter()
|
||||
@@ -82,7 +82,7 @@ fn Post(cx: Scope) -> impl IntoView {
|
||||
});
|
||||
|
||||
let post_view = move || {
|
||||
post.with(|post| {
|
||||
post.with(cx, |post| {
|
||||
post.clone().map(|post| {
|
||||
view! { cx,
|
||||
// render content
|
||||
|
||||
@@ -140,7 +140,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
||||
{move || {
|
||||
let existing_todos = {
|
||||
move || {
|
||||
todos.read()
|
||||
todos.read(cx)
|
||||
.map(move |todos| match todos {
|
||||
Err(e) => {
|
||||
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
|
||||
|
||||
@@ -159,7 +159,7 @@ pub fn Todos(cx: Scope) -> impl IntoView {
|
||||
{move || {
|
||||
let existing_todos = {
|
||||
move || {
|
||||
todos.read()
|
||||
todos.read(cx)
|
||||
.map(move |todos| match todos {
|
||||
Err(e) => {
|
||||
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}.into_any()]
|
||||
|
||||
@@ -251,11 +251,6 @@ async fn handle_server_fns_inner(
|
||||
res_options_inner.headers.clone(),
|
||||
);
|
||||
|
||||
if let Some(header_ref) = res.headers_mut()
|
||||
{
|
||||
header_ref.extend(res_headers.drain());
|
||||
};
|
||||
|
||||
if accept_header == Some("application/json")
|
||||
|| accept_header
|
||||
== Some(
|
||||
@@ -285,6 +280,12 @@ async fn handle_server_fns_inner(
|
||||
Some(status) => res.status(status),
|
||||
None => res,
|
||||
};
|
||||
// This must be after the default referrer
|
||||
// redirect so that it overwrites the one above
|
||||
if let Some(header_ref) = res.headers_mut()
|
||||
{
|
||||
header_ref.extend(res_headers.drain());
|
||||
};
|
||||
match serialized {
|
||||
Payload::Binary(data) => res
|
||||
.header(
|
||||
|
||||
@@ -28,7 +28,7 @@ use std::rc::Rc;
|
||||
/// <div>
|
||||
/// <Suspense fallback=move || view! { cx, <p>"Loading (Suspense Fallback)..."</p> }>
|
||||
/// {move || {
|
||||
/// cats.read().map(|data| match data {
|
||||
/// cats.read(cx).map(|data| match data {
|
||||
/// None => view! { cx, <pre>"Error"</pre> }.into_any(),
|
||||
/// Some(cats) => view! { cx,
|
||||
/// <div>{
|
||||
@@ -69,7 +69,8 @@ where
|
||||
|
||||
let orig_child = Rc::new(children);
|
||||
|
||||
let current_id = HydrationCtx::peek();
|
||||
let before_me = HydrationCtx::peek();
|
||||
let current_id = HydrationCtx::next_component();
|
||||
|
||||
let child = DynChild::new({
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
@@ -142,5 +143,7 @@ where
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
HydrationCtx::continue_from(before_me);
|
||||
|
||||
leptos_dom::View::Suspense(current_id, core_component)
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||
/// set_pending=set_pending.into()
|
||||
/// >
|
||||
/// {move || {
|
||||
/// cats.read().map(|data| match data {
|
||||
/// cats.read(cx).map(|data| match data {
|
||||
/// None => view! { cx, <pre>"Error"</pre> }.into_any(),
|
||||
/// Some(cats) => view! { cx,
|
||||
/// <div>{
|
||||
|
||||
@@ -4,55 +4,55 @@ use leptos::*;
|
||||
|
||||
#[component]
|
||||
pub fn App(cx: Scope) -> impl IntoView {
|
||||
let pending_thing = create_resource(
|
||||
cx,
|
||||
|| false,
|
||||
|_| async {
|
||||
if cfg!(feature = "ssr") {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
spawn_local(async {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
tx.send(());
|
||||
});
|
||||
rx.await;
|
||||
} else {
|
||||
}
|
||||
true
|
||||
},
|
||||
);
|
||||
let pending_thing = create_resource(
|
||||
cx,
|
||||
|| false,
|
||||
|_| async {
|
||||
if cfg!(feature = "ssr") {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
spawn_local(async {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
tx.send(());
|
||||
});
|
||||
rx.await;
|
||||
} else {
|
||||
}
|
||||
true
|
||||
},
|
||||
);
|
||||
|
||||
view! { cx,
|
||||
view! { cx,
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
"This is some text"
|
||||
</div>
|
||||
// <Suspense fallback=move || view! { cx, <p>"Loading..."</p> }>
|
||||
{move || pending_thing.read().map(|n| view! { cx, <ComponentA/> })}
|
||||
// </Suspense>
|
||||
"This is some text"
|
||||
</div>
|
||||
}
|
||||
// <Suspense fallback=move || view! { cx, <p>"Loading..."</p> }>
|
||||
{move || pending_thing.read().map(|n| view! { cx, <ComponentA/> })}
|
||||
// </Suspense>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ComponentA(cx: Scope) -> impl IntoView {
|
||||
let (value, set_value) = create_signal(cx, "Hello?".to_string());
|
||||
let (counter, set_counter) = create_signal(cx, 0);
|
||||
let (value, set_value) = create_signal(cx, "Hello?".to_string());
|
||||
let (counter, set_counter) = create_signal(cx, 0);
|
||||
|
||||
// Test to make sure hydration isn't broken by
|
||||
// something like this
|
||||
//let _ = [div(cx)].into_view(cx);
|
||||
// Test to make sure hydration isn't broken by
|
||||
// something like this
|
||||
//let _ = [div(cx)].into_view(cx);
|
||||
|
||||
div(cx)
|
||||
.id("the-div")
|
||||
.child(
|
||||
input(cx)
|
||||
.attr("type", "text")
|
||||
.prop("value", (cx, value))
|
||||
.on(ev::input, move |e| set_value(event_target_value(&e))),
|
||||
)
|
||||
.child(input(cx).attr("type", "text").prop("value", value))
|
||||
.child(p(cx).child("Value: ").child(value))
|
||||
.into_view(cx)
|
||||
div(cx)
|
||||
.id("the-div")
|
||||
.child(
|
||||
input(cx)
|
||||
.attr("type", "text")
|
||||
.prop("value", (cx, value))
|
||||
.on(ev::input, move |e| set_value(event_target_value(&e))),
|
||||
)
|
||||
.child(input(cx).attr("type", "text").prop("value", value))
|
||||
.child(p(cx).child("Value: ").child(value))
|
||||
.into_view(cx)
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
@@ -61,11 +61,11 @@ use wasm_bindgen::prelude::wasm_bindgen;
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
console_error_panic_hook::set_once();
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
gloo::console::debug!("starting WASM");
|
||||
gloo::console::debug!("starting WASM");
|
||||
|
||||
leptos::mount_to_body(move |cx| {
|
||||
view! { cx, <App/> }
|
||||
});
|
||||
leptos::mount_to_body(move |cx| {
|
||||
view! { cx, <App/> }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ gloo = { version = "0.8", features = ["futures"] }
|
||||
leptos = { path = "../../../leptos", features = ["tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tracing-subscriber-wasm = "0.1"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = "0.3"
|
||||
|
||||
|
||||
@@ -1,42 +1,102 @@
|
||||
#![allow(warnings)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate tracing;
|
||||
|
||||
mod utils;
|
||||
|
||||
use leptos::*;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing::field::debug;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
fn main() {
|
||||
tracing_subscriber::fmt()
|
||||
.with_writer(tracing_subscriber_wasm::MakeConsoleWriter::default())
|
||||
.without_time()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.pretty()
|
||||
.with_target(false)
|
||||
.init();
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
mount_to_body(app);
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.without_time()
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_target(false)
|
||||
.with_writer(utils::MakeConsoleWriter)
|
||||
.with_ansi(false)
|
||||
.pretty()
|
||||
.finish()
|
||||
.init();
|
||||
|
||||
mount_to_body(view_fn);
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn app(cx: Scope) -> impl IntoView {
|
||||
let (data, set_data) = create_signal(cx, vec![1, 3, 5]);
|
||||
fn view_fn(cx: Scope) -> impl IntoView {
|
||||
let view = view! { cx,
|
||||
<For
|
||||
each=|| vec![0, 1, 2, 3, 4, 5, 6, 7]
|
||||
key=|i| *i
|
||||
view=|cx, i| view! { cx, {i} }
|
||||
/>
|
||||
}
|
||||
.into_view(cx);
|
||||
|
||||
let handle_change = move |_| {
|
||||
set_data.update(|data| {
|
||||
if [1, 3, 5] == data[..] {
|
||||
*data = vec![0, 1, 2, 3, 4, 5, 6];
|
||||
} else {
|
||||
*data = vec![1, 3, 5];
|
||||
}
|
||||
})
|
||||
};
|
||||
let (a, set_a) = create_signal(cx, view.clone());
|
||||
let (b, set_b) = create_signal(cx, view);
|
||||
|
||||
view! { cx,
|
||||
<button on:click=handle_change>"Reverse"</button>
|
||||
let (is_a, set_is_a) = create_signal(cx, true);
|
||||
|
||||
<For
|
||||
each=data
|
||||
key=|item| *item
|
||||
view=|cx, i| view! { cx, <h3>{i}</h3> }
|
||||
/>
|
||||
let handle_toggle = move |_| {
|
||||
trace!("toggling");
|
||||
if is_a() {
|
||||
set_b(a());
|
||||
|
||||
set_is_a(false);
|
||||
} else {
|
||||
set_a(a());
|
||||
|
||||
set_is_a(true);
|
||||
}
|
||||
};
|
||||
|
||||
let a_tag = view! { cx, <svg::a/> };
|
||||
|
||||
view! { cx,
|
||||
<>
|
||||
<div>
|
||||
<button on:click=handle_toggle>"Toggle"</button>
|
||||
</div>
|
||||
<svg>{a_tag}</svg>
|
||||
<Example/>
|
||||
<A child=Signal::from(a) />
|
||||
<A child=Signal::from(b) />
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn A(cx: Scope, child: Signal<View>) -> impl IntoView {
|
||||
move || child()
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Example(cx: Scope) -> impl IntoView {
|
||||
trace!("rendering <Example/>");
|
||||
|
||||
let (value, set_value) = create_signal(cx, 10);
|
||||
|
||||
let memo = create_memo(cx, move |_| value() * 2);
|
||||
let derived = Signal::derive(cx, move || value() * 3);
|
||||
|
||||
create_effect(cx, move |_| {
|
||||
trace!("logging value of derived..., {}", derived.get());
|
||||
});
|
||||
|
||||
set_timeout(
|
||||
move || set_value.update(|v| *v += 1),
|
||||
std::time::Duration::from_millis(50),
|
||||
);
|
||||
|
||||
view! { cx,
|
||||
<h1>"Example"</h1>
|
||||
<button on:click=move |_| set_value.update(|value| *value += 1)>
|
||||
"Click me"
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
47
leptos_dom/examples/test-bench/src/utils.rs
Normal file
47
leptos_dom/examples/test-bench/src/utils.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
pub struct MakeConsoleWriter;
|
||||
use std::io::{self, Write};
|
||||
use tracing_subscriber::fmt::MakeWriter;
|
||||
|
||||
impl<'a> MakeWriter<'a> for MakeConsoleWriter {
|
||||
type Writer = ConsoleWriter;
|
||||
|
||||
fn make_writer(&'a self) -> Self::Writer {
|
||||
unimplemented!("use make_writer_for instead");
|
||||
}
|
||||
|
||||
fn make_writer_for(&'a self, meta: &tracing::Metadata<'_>) -> Self::Writer {
|
||||
ConsoleWriter(*meta.level(), Vec::with_capacity(256))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConsoleWriter(tracing::Level, Vec<u8>);
|
||||
|
||||
impl io::Write for ConsoleWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.1.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
use gloo::console;
|
||||
use tracing::Level;
|
||||
|
||||
let data = String::from_utf8(self.1.to_owned())
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "data not UTF-8"))?;
|
||||
|
||||
match self.0 {
|
||||
Level::TRACE => console::debug!(&data),
|
||||
Level::DEBUG => console::debug!(&data),
|
||||
Level::INFO => console::log!(&data),
|
||||
Level::WARN => console::warn!(&data),
|
||||
Level::ERROR => console::error!(&data),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConsoleWriter {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.flush();
|
||||
}
|
||||
}
|
||||
@@ -347,72 +347,60 @@ where
|
||||
let (children, closing) =
|
||||
(component.children.clone(), component.closing.node.clone());
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
{
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
|
||||
create_effect(cx, move |prev_hash_run| {
|
||||
let mut children_borrow = children.borrow_mut();
|
||||
let mut children_borrow = children.borrow_mut();
|
||||
|
||||
let opening = if let Some(Some(child)) = children_borrow.get(0)
|
||||
{
|
||||
child.get_opening_node()
|
||||
} else {
|
||||
closing.clone()
|
||||
};
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
let opening = if let Some(Some(child)) = children_borrow.get(0) {
|
||||
child.get_opening_node()
|
||||
} else {
|
||||
closing.clone()
|
||||
};
|
||||
|
||||
let items = items_fn();
|
||||
let items = items_fn();
|
||||
|
||||
let items = items.into_iter().collect::<SmallVec<[_; 128]>>();
|
||||
let items = items.into_iter().collect::<SmallVec<[_; 128]>>();
|
||||
|
||||
let hashed_items =
|
||||
items.iter().map(&key_fn).collect::<FxIndexSet<_>>();
|
||||
let hashed_items =
|
||||
items.iter().map(&key_fn).collect::<FxIndexSet<_>>();
|
||||
|
||||
if let Some(HashRun(prev_hash_run)) = prev_hash_run {
|
||||
let cmds = diff(&prev_hash_run, &hashed_items);
|
||||
if let Some(HashRun(prev_hash_run)) = prev_hash_run {
|
||||
let cmds = diff(&prev_hash_run, &hashed_items);
|
||||
|
||||
tracing::debug!("cmds:\n{cmds:#?}");
|
||||
apply_cmds(
|
||||
cx,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
&opening,
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
&closing,
|
||||
cmds,
|
||||
&mut children_borrow,
|
||||
items.into_iter().map(|t| Some(t)).collect(),
|
||||
&each_fn
|
||||
);
|
||||
} else {
|
||||
*children_borrow = Vec::with_capacity(items.len());
|
||||
|
||||
apply_cmds(
|
||||
cx,
|
||||
&opening,
|
||||
&closing,
|
||||
cmds,
|
||||
&mut children_borrow,
|
||||
items.into_iter().map(|t| Some(t)).collect(),
|
||||
&each_fn,
|
||||
);
|
||||
} else {
|
||||
children_borrow.clear();
|
||||
children_borrow.reserve(items.len());
|
||||
for item in items {
|
||||
let (each_item, _) = cx.run_child_scope(|cx| EachItem::new(cx, each_fn(cx, item).into_view(cx)));
|
||||
|
||||
for item in items {
|
||||
let (each_item, disposer) = cx.run_child_scope(|cx| {
|
||||
EachItem::new(cx, each_fn(cx, item).into_view(cx))
|
||||
});
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
mount_child(MountKind::Before(&closing), &each_item);
|
||||
|
||||
mount_child(MountKind::Before(&closing), &each_item);
|
||||
|
||||
children_borrow.push(Some(each_item));
|
||||
}
|
||||
children_borrow.push(Some(each_item));
|
||||
}
|
||||
}
|
||||
|
||||
HashRun(hashed_items)
|
||||
HashRun(hashed_items)
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
|
||||
{
|
||||
} else {
|
||||
*component.children.borrow_mut() = (items_fn)()
|
||||
.into_iter()
|
||||
.map(|child| {
|
||||
cx.run_child_scope(|cx| {
|
||||
Some(EachItem::new(
|
||||
cx,
|
||||
(each_fn)(cx, child).into_view(cx),
|
||||
))
|
||||
})
|
||||
.0
|
||||
})
|
||||
.collect();
|
||||
.into_iter()
|
||||
.map(|child| cx.run_child_scope(|cx| Some(EachItem::new(cx, (each_fn)(cx, child).into_view(cx)))).0)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
View::CoreComponent(CoreComponent::Each(component))
|
||||
@@ -423,7 +411,7 @@ where
|
||||
#[educe(Debug)]
|
||||
struct HashRun<T>(#[educe(Debug(ignore))] T);
|
||||
|
||||
/// Calculates the operations needed to get from `a` to `b`.
|
||||
/// Calculates the operations need to get from `a` to `b`.
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
|
||||
if from.is_empty() && to.is_empty() {
|
||||
@@ -436,73 +424,72 @@ fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
|
||||
}
|
||||
|
||||
// Get removed items
|
||||
let mut removed =
|
||||
from.difference(to).map(|k| from.get_index_of(k).unwrap());
|
||||
let mut removed = from.difference(to);
|
||||
|
||||
let removed_cmds = removed.clone().map(|idx| DiffOpRemove { at: idx });
|
||||
let removed_cmds = removed
|
||||
.clone()
|
||||
.map(|k| from.get_full(k).unwrap().0)
|
||||
.map(|idx| DiffOpRemove { at: idx });
|
||||
|
||||
// Get added items
|
||||
let mut added = to.difference(from).map(|k| to.get_index_of(k).unwrap());
|
||||
let mut added = to.difference(from);
|
||||
|
||||
let added_cmds = added.clone().map(|idx| DiffOpAdd {
|
||||
at: idx,
|
||||
mode: Default::default(),
|
||||
});
|
||||
let added_cmds =
|
||||
added
|
||||
.clone()
|
||||
.map(|k| to.get_full(k).unwrap().0)
|
||||
.map(|idx| DiffOpAdd {
|
||||
at: idx,
|
||||
mode: Default::default(),
|
||||
});
|
||||
|
||||
let mut normalized_idx = 0i64;
|
||||
let mut next_added_idx = added.next();
|
||||
let mut next_removed_idx = removed.next();
|
||||
// Get moved items
|
||||
let mut normalized_idx = 0;
|
||||
let mut move_cmds = SmallVec::<[_; 8]>::with_capacity(to.len());
|
||||
let mut added_idx = added.next().map(|k| to.get_full(k).unwrap().0);
|
||||
let mut removed_idx = removed.next().map(|k| from.get_full(k).unwrap().0);
|
||||
|
||||
let move_cmds = to
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, k)| {
|
||||
let is_added = if let Some(idx) = next_added_idx {
|
||||
if i == idx {
|
||||
next_added_idx = added.next();
|
||||
normalized_idx -= 1;
|
||||
for (idx, k) in to.iter().enumerate() {
|
||||
if let Some(added_idx) = added_idx.as_mut().filter(|r_i| **r_i == idx) {
|
||||
if let Some(next_added) =
|
||||
added.next().map(|k| to.get_full(k).unwrap().0)
|
||||
{
|
||||
*added_idx = next_added;
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let is_removed = if let Some(idx) = next_removed_idx {
|
||||
if i == idx {
|
||||
next_removed_idx = removed.next();
|
||||
normalized_idx += 1;
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
normalized_idx += 1;
|
||||
|
||||
if !is_added && !is_removed {
|
||||
Some((
|
||||
from.get_index_of(k).unwrap(),
|
||||
i,
|
||||
// We need to `-1` because otherwise, we'd be accounting for
|
||||
// the NEXT iteration, not this current one
|
||||
normalized_idx - 1,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
normalized_idx = usize::wrapping_sub(normalized_idx, 1);
|
||||
}
|
||||
})
|
||||
.map(|(from, to, normalized_idx)| DiffOpMove {
|
||||
from,
|
||||
to,
|
||||
move_in_dom: to != normalized_idx as usize,
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
if let Some(removed_idx) =
|
||||
removed_idx.as_mut().filter(|r_i| **r_i == idx)
|
||||
{
|
||||
normalized_idx = normalized_idx.wrapping_add(1);
|
||||
|
||||
if let Some(next_removed) =
|
||||
removed.next().map(|k| from.get_full(k).unwrap().0)
|
||||
{
|
||||
*removed_idx = next_removed;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((from_idx, _)) = from.get_full(k) {
|
||||
if from_idx != normalized_idx {
|
||||
move_cmds.push(DiffOpMove {
|
||||
from: from_idx,
|
||||
to: idx,
|
||||
move_in_dom: true,
|
||||
});
|
||||
} else if from_idx != idx {
|
||||
move_cmds.push(DiffOpMove {
|
||||
from: from_idx,
|
||||
to: idx,
|
||||
move_in_dom: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
normalized_idx = normalized_idx.wrapping_add(1);
|
||||
}
|
||||
|
||||
let mut diffs = Diff {
|
||||
removed: removed_cmds.collect(),
|
||||
@@ -634,16 +621,17 @@ fn apply_cmds<T, EF, N>(
|
||||
if opening.previous_sibling().is_none()
|
||||
&& closing.next_sibling().is_none()
|
||||
{
|
||||
if let Some(parent) = closing
|
||||
let parent = closing
|
||||
.parent_node()
|
||||
.map(JsCast::unchecked_into::<web_sys::Element>)
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
parent.append_with_node_2(opening, closing).unwrap();
|
||||
.expect("could not get closing node")
|
||||
.unchecked_into::<web_sys::Element>();
|
||||
parent.set_text_content(Some(""));
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
parent.append_with_node_1(closing).unwrap();
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
parent.append_with_node_2(opening, closing).unwrap();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
parent.append_with_node_1(closing).unwrap();
|
||||
} else {
|
||||
range.set_start_before(opening).unwrap();
|
||||
range.set_end_before(closing).unwrap();
|
||||
@@ -676,7 +664,7 @@ fn apply_cmds<T, EF, N>(
|
||||
for DiffOpAdd { at, mode } in cmds.added {
|
||||
let item = items[at].take().unwrap();
|
||||
|
||||
let (each_item, disposer) = cx.run_child_scope(|cx| {
|
||||
let (each_item, _) = cx.run_child_scope(|cx| {
|
||||
let child = each_fn(cx, item).into_view(cx);
|
||||
EachItem::new(cx, child)
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ cfg_if! {
|
||||
}
|
||||
}
|
||||
|
||||
/// A stable identifer within the server-rendering or hydration process.
|
||||
/// A stable identifier within the server-rendering or hydration process.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct HydrationKey {
|
||||
/// The key of the previous component.
|
||||
|
||||
@@ -760,7 +760,7 @@ pub fn window() -> web_sys::Window {
|
||||
|
||||
/// Returns the [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document).
|
||||
///
|
||||
/// This is cached as a thread-local variable, so calling `window()` multiple times
|
||||
/// This is cached as a thread-local variable, so calling `document()` multiple times
|
||||
/// requires only one call out to JavaScript.
|
||||
pub fn document() -> web_sys::Document {
|
||||
DOCUMENT.with(|document| document.clone())
|
||||
|
||||
@@ -54,11 +54,11 @@ use std::{
|
||||
/// // when we read the signal, it contains either
|
||||
/// // 1) None (if the Future isn't ready yet) or
|
||||
/// // 2) Some(T) (if the future's already resolved)
|
||||
/// assert_eq!(cats(), Some(vec!["1".to_string()]));
|
||||
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
|
||||
///
|
||||
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
|
||||
/// set_how_many_cats(2);
|
||||
/// assert_eq!(cats(), Some(vec!["2".to_string()]));
|
||||
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
|
||||
/// # }
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
@@ -121,7 +121,6 @@ where
|
||||
let source = create_memo(cx, move |_| source());
|
||||
|
||||
let r = Rc::new(ResourceState {
|
||||
scope: cx,
|
||||
value,
|
||||
set_value,
|
||||
loading,
|
||||
@@ -245,7 +244,6 @@ where
|
||||
let source = create_memo(cx, move |_| source());
|
||||
|
||||
let r = Rc::new(ResourceState {
|
||||
scope: cx,
|
||||
value,
|
||||
set_value,
|
||||
loading,
|
||||
@@ -371,14 +369,14 @@ where
|
||||
/// resource.
|
||||
///
|
||||
/// If you want to get the value without cloning it, use [Resource::with].
|
||||
/// (`value.read()` is equivalent to `value.with(T::clone)`.)
|
||||
pub fn read(&self) -> Option<T>
|
||||
/// (`value.read(cx)` is equivalent to `value.with(cx, T::clone)`.)
|
||||
pub fn read(&self, cx: Scope) -> Option<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
resource.read()
|
||||
resource.read(cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
@@ -392,10 +390,10 @@ where
|
||||
///
|
||||
/// If you want to get the value by cloning it, you can use
|
||||
/// [Resource::read].
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
resource.with(f)
|
||||
resource.with(cx, f)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
@@ -427,13 +425,16 @@ where
|
||||
/// Returns a [std::future::Future] that will resolve when the resource has loaded,
|
||||
/// yield its [ResourceId] and a JSON string.
|
||||
#[cfg(any(feature = "ssr", doc))]
|
||||
pub async fn to_serialization_resolver(&self) -> (ResourceId, String)
|
||||
pub async fn to_serialization_resolver(
|
||||
&self,
|
||||
cx: Scope,
|
||||
) -> (ResourceId, String)
|
||||
where
|
||||
T: Serializable,
|
||||
{
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.resource(self.id, |resource: &ResourceState<S, T>| {
|
||||
resource.to_serialization_resolver(self.id)
|
||||
resource.to_serialization_resolver(cx, self.id)
|
||||
})
|
||||
})
|
||||
.expect(
|
||||
@@ -479,11 +480,11 @@ where
|
||||
/// // when we read the signal, it contains either
|
||||
/// // 1) None (if the Future isn't ready yet) or
|
||||
/// // 2) Some(T) (if the future's already resolved)
|
||||
/// assert_eq!(cats(), Some(vec!["1".to_string()]));
|
||||
/// assert_eq!(cats.read(cx), Some(vec!["1".to_string()]));
|
||||
///
|
||||
/// // when the signal's value changes, the `Resource` will generate and run a new `Future`
|
||||
/// set_how_many_cats(2);
|
||||
/// assert_eq!(cats(), Some(vec!["2".to_string()]));
|
||||
/// assert_eq!(cats.read(cx), Some(vec!["2".to_string()]));
|
||||
/// # }
|
||||
/// # }).dispose();
|
||||
/// ```
|
||||
@@ -531,48 +532,12 @@ where
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> FnOnce<()> for Resource<S, T>
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
type Output = Option<T>;
|
||||
|
||||
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> FnMut<()> for Resource<S, T>
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "stable"))]
|
||||
impl<S, T> Fn<()> for Resource<S, T>
|
||||
where
|
||||
S: Clone + 'static,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
extern "rust-call" fn call(&self, _args: ()) -> Self::Output {
|
||||
self.read()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ResourceState<S, T>
|
||||
where
|
||||
S: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
scope: Scope,
|
||||
value: ReadSignal<Option<T>>,
|
||||
set_value: WriteSignal<Option<T>>,
|
||||
pub loading: ReadSignal<bool>,
|
||||
@@ -590,15 +555,15 @@ where
|
||||
S: Clone + 'static,
|
||||
T: 'static,
|
||||
{
|
||||
pub fn read(&self) -> Option<T>
|
||||
pub fn read(&self, cx: Scope) -> Option<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.with(T::clone)
|
||||
self.with(cx, T::clone)
|
||||
}
|
||||
|
||||
pub fn with<U>(&self, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
let suspense_cx = use_context::<SuspenseContext>(self.scope);
|
||||
pub fn with<U>(&self, cx: Scope, f: impl FnOnce(&T) -> U) -> Option<U> {
|
||||
let suspense_cx = use_context::<SuspenseContext>(cx);
|
||||
|
||||
let v = self
|
||||
.value
|
||||
@@ -611,21 +576,23 @@ where
|
||||
|
||||
let increment = move |_: Option<()>| {
|
||||
if let Some(s) = &suspense_cx {
|
||||
let mut contexts = suspense_contexts.borrow_mut();
|
||||
if !contexts.contains(s) {
|
||||
contexts.insert(*s);
|
||||
if let Ok(ref mut contexts) = suspense_contexts.try_borrow_mut()
|
||||
{
|
||||
if !contexts.contains(s) {
|
||||
contexts.insert(*s);
|
||||
|
||||
// on subsequent reads, increment will be triggered in load()
|
||||
// because the context has been tracked here
|
||||
// on the first read, resource is already loading without having incremented
|
||||
if !has_value {
|
||||
s.increment();
|
||||
// on subsequent reads, increment will be triggered in load()
|
||||
// because the context has been tracked here
|
||||
// on the first read, resource is already loading without having incremented
|
||||
if !has_value {
|
||||
s.increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
create_isomorphic_effect(self.scope, increment);
|
||||
create_isomorphic_effect(cx, increment);
|
||||
v
|
||||
}
|
||||
|
||||
@@ -685,6 +652,7 @@ where
|
||||
|
||||
pub fn resource_to_serialization_resolver(
|
||||
&self,
|
||||
cx: Scope,
|
||||
id: ResourceId,
|
||||
) -> std::pin::Pin<Box<dyn futures::Future<Output = (ResourceId, String)>>>
|
||||
where
|
||||
@@ -694,7 +662,7 @@ where
|
||||
|
||||
let (tx, mut rx) = futures::channel::mpsc::channel(1);
|
||||
let value = self.value;
|
||||
create_isomorphic_effect(self.scope, move |_| {
|
||||
create_isomorphic_effect(cx, move |_| {
|
||||
value.with({
|
||||
let mut tx = tx.clone();
|
||||
move |value| {
|
||||
@@ -731,6 +699,7 @@ pub(crate) trait SerializableResource {
|
||||
|
||||
fn to_serialization_resolver(
|
||||
&self,
|
||||
cx: Scope,
|
||||
id: ResourceId,
|
||||
) -> Pin<Box<dyn Future<Output = (ResourceId, String)>>>;
|
||||
}
|
||||
@@ -746,9 +715,10 @@ where
|
||||
|
||||
fn to_serialization_resolver(
|
||||
&self,
|
||||
cx: Scope,
|
||||
id: ResourceId,
|
||||
) -> Pin<Box<dyn Future<Output = (ResourceId, String)>>> {
|
||||
let fut = self.resource_to_serialization_resolver(id);
|
||||
let fut = self.resource_to_serialization_resolver(cx, id);
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,11 +421,12 @@ impl Runtime {
|
||||
|
||||
pub(crate) fn serialization_resolvers(
|
||||
&self,
|
||||
cx: Scope,
|
||||
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
let f = FuturesUnordered::new();
|
||||
for (id, resource) in self.resources.borrow().iter() {
|
||||
if let AnyResource::Serializable(resource) = resource {
|
||||
f.push(resource.to_serialization_resolver(id));
|
||||
f.push(resource.to_serialization_resolver(cx, id));
|
||||
}
|
||||
}
|
||||
f
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![forbid(unsafe_code)]
|
||||
use crate::{
|
||||
console_warn,
|
||||
runtime::{with_runtime, RuntimeId},
|
||||
suspense::StreamChunk,
|
||||
EffectId, PinnedFuture, ResourceId, SignalId, SuspenseContext,
|
||||
@@ -266,10 +267,13 @@ impl Scope {
|
||||
) {
|
||||
_ = with_runtime(self.runtime, |runtime| {
|
||||
let scopes = runtime.scopes.borrow();
|
||||
let scope = scopes.get(self.id).expect(
|
||||
"tried to add property to a scope that has been disposed",
|
||||
);
|
||||
f(&mut scope.borrow_mut());
|
||||
if let Some(scope) = scopes.get(self.id) {
|
||||
f(&mut scope.borrow_mut());
|
||||
} else {
|
||||
console_warn(
|
||||
"tried to add property to a scope that has been disposed",
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -353,8 +357,10 @@ impl Scope {
|
||||
pub fn serialization_resolvers(
|
||||
&self,
|
||||
) -> FuturesUnordered<PinnedFuture<(ResourceId, String)>> {
|
||||
with_runtime(self.runtime, |runtime| runtime.serialization_resolvers())
|
||||
.unwrap_or_default()
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
runtime.serialization_resolvers(*self)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Registers the given [SuspenseContext](crate::SuspenseContext) with the current scope,
|
||||
@@ -409,7 +415,9 @@ impl Scope {
|
||||
{
|
||||
with_runtime(self.runtime, |runtime| {
|
||||
let mut shared_context = runtime.shared_context.borrow_mut();
|
||||
std::mem::take(&mut shared_context.pending_fragments)
|
||||
let f = std::mem::take(&mut shared_context.pending_fragments);
|
||||
println!("pending_fragments = {}", f.len());
|
||||
f
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ where
|
||||
|
||||
/// Updates whether the action is currently pending.
|
||||
pub fn set_pending(&self, pending: bool) {
|
||||
self.0.with_value(|a| a.pending.set(pending))
|
||||
self.0.try_with_value(|a| a.pending.set(pending));
|
||||
}
|
||||
|
||||
/// The URL associated with the action (typically as part of a server function.)
|
||||
|
||||
@@ -12,7 +12,7 @@ description = "Router for the Leptos web framework."
|
||||
leptos = { workspace = true }
|
||||
cfg-if = "1"
|
||||
common_macros = "0.1"
|
||||
gloo-net = "0.2"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
lazy_static = "1"
|
||||
linear-map = "1"
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::{use_navigate, use_resolved_path, ToHref};
|
||||
use crate::{use_navigate, use_resolved_path, ToHref, Url};
|
||||
use leptos::*;
|
||||
use std::{error::Error, rc::Rc};
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::RequestRedirect;
|
||||
|
||||
type OnFormData = Rc<dyn Fn(&web_sys::FormData)>;
|
||||
type OnResponse = Rc<dyn Fn(&web_sys::Response)>;
|
||||
@@ -90,12 +91,13 @@ where
|
||||
let res = gloo_net::http::Request::post(&action)
|
||||
.header("Accept", "application/json")
|
||||
.header("Content-Type", &enctype)
|
||||
.redirect(RequestRedirect::Follow)
|
||||
.body(params)
|
||||
.send()
|
||||
.await;
|
||||
match res {
|
||||
Err(e) => {
|
||||
log::error!("<Form/> error while POSTing: {e:#?}");
|
||||
error!("<Form/> error while POSTing: {e:#?}");
|
||||
if let Some(error) = error {
|
||||
error.set(Some(Box::new(e)));
|
||||
}
|
||||
@@ -110,15 +112,22 @@ where
|
||||
if let Some(on_response) = on_response.clone() {
|
||||
on_response(resp.as_raw());
|
||||
}
|
||||
|
||||
if resp.status() == 303 {
|
||||
if let Some(redirect_url) =
|
||||
resp.headers().get("Location")
|
||||
{
|
||||
_ = navigate(
|
||||
&redirect_url,
|
||||
Default::default(),
|
||||
);
|
||||
// Check all the logical 3xx responses that might
|
||||
// get returned from a server function
|
||||
if resp.redirected() {
|
||||
let resp_url = &resp.url();
|
||||
match Url::try_from(resp_url.as_str()) {
|
||||
Ok(url) => {
|
||||
request_animation_frame(move || {
|
||||
if let Err(e) = navigate(
|
||||
&url.pathname,
|
||||
Default::default(),
|
||||
) {
|
||||
warn!("{}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => warn!("{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,7 +216,7 @@ where
|
||||
input.set(Some(data));
|
||||
action.set_pending(true);
|
||||
}
|
||||
Err(e) => log::error!("{e}"),
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -225,15 +234,19 @@ where
|
||||
.as_string()
|
||||
.expect("couldn't get String from JsString"),
|
||||
) {
|
||||
Ok(res) => value.set(Some(Ok(res))),
|
||||
Err(e) => value.set(Some(Err(
|
||||
ServerFnError::Deserialization(e.to_string()),
|
||||
))),
|
||||
Ok(res) => {
|
||||
value.try_set(Some(Ok(res)));
|
||||
}
|
||||
Err(e) => {
|
||||
value.try_set(Some(Err(
|
||||
ServerFnError::Deserialization(e.to_string()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("{e:?}"),
|
||||
Err(e) => error!("{e:?}"),
|
||||
};
|
||||
input.set(None);
|
||||
input.try_set(None);
|
||||
action.set_pending(false);
|
||||
});
|
||||
});
|
||||
@@ -293,7 +306,7 @@ where
|
||||
let form_data = web_sys::FormData::new_with_form(&form).unwrap_throw();
|
||||
let data = action_input_from_form_data(&form_data);
|
||||
match data {
|
||||
Err(e) => log::error!("{e}"),
|
||||
Err(e) => error!("{e}"),
|
||||
Ok(input) => {
|
||||
ev.prevent_default();
|
||||
multi_action.dispatch(input);
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
//! <div>
|
||||
//! // show the contacts
|
||||
//! <ul>
|
||||
//! {move || contacts.read().map(|contacts| view! { cx, <li>"todo contact info"</li> } )}
|
||||
//! {move || contacts.read(cx).map(|contacts| view! { cx, <li>"todo contact info"</li> } )}
|
||||
//! </ul>
|
||||
//!
|
||||
//! // insert the nested child route here
|
||||
|
||||
Reference in New Issue
Block a user