Compare commits

..

21 Commits

Author SHA1 Message Date
Greg Johnston
0798e0812d fix: unused import in example 2024-10-01 21:23:22 -04:00
Greg Johnston
03514e68be chore: fix test import 2024-10-01 20:43:46 -04:00
Greg Johnston
4092368581 chore: remove unused import 2024-10-01 20:43:37 -04:00
Greg Johnston
896f7de8e1 fix: update import of spawn_local in test examples 2024-10-01 20:06:04 -04:00
Greg Johnston
29b2d3024a fix: update import of tick in test examples 2024-10-01 20:05:22 -04:00
Greg Johnston
0d4f3b51e9 fix: import of reactive_graph in integrations 2024-10-01 19:38:55 -04:00
Greg Johnston
2431b19cdf chore: reexport reactive_graph as leptos::reactive 2024-10-01 19:36:20 -04:00
Greg Johnston
4801e1ec6d chore: hide unwrap_signal from docs 2024-10-01 19:36:20 -04:00
Greg Johnston
e206b93ba5 chore: inline docs for reexported crates 2024-10-01 19:36:20 -04:00
Greg Johnston
f0675446d8 chore: reexport reactive_graph as leptos::reactive 2024-10-01 19:36:20 -04:00
Greg Johnston
2ac7eaad15 chore: add missing docs for tick 2024-10-01 19:36:20 -04:00
Greg Johnston
8a040fda69 chore: rename Writeable to Write for consistency 2024-10-01 19:36:20 -04:00
Greg Johnston
2f5c966cf4 chore: reexport untrack from leptos::prelude but not from reactive_graph 2024-10-01 19:36:20 -04:00
Greg Johnston
45e2629f0e chore: remove reexport of tachys in leptos::prelude 2024-10-01 19:36:20 -04:00
Greg Johnston
517566d085 change: rename spawn module to task 2024-10-01 19:35:45 -04:00
Greg Johnston
6df8657700 fix: escape text nodes and attributes in InertHtml (closes #3056) (#3057) 2024-10-01 19:19:21 -04:00
Georg Vienna
2a4063a259 projects: port session_auth_axum (#2970) 2024-10-01 09:40:57 -07:00
Marc-Stefan Cassola
013ec4a09d Two-way data binding (#2977)
* added two-way data binding to dom elements

* added two-way data binding to radio groups

* moved bind into reactive_graph mod

* chore: use `::leptos` absolute path in macro

* chore: move `Group` into the reactive module, as that's the only place it has meaning

* feat: use new `bind:` syntax

* chore: update for non-generic rendering

* chore: ignore this test

---------

Co-authored-by: Greg Johnston <greg.johnston@gmail.com>
2024-10-01 09:39:45 -07:00
Greg Johnston
d10fec08e2 Merge pull request #3037 from leptos-rs/rename-island-router
change: rename island-router feature so people don't use it
2024-09-30 20:55:08 -04:00
Greg Johnston
94f4328586 example: add README that actually explains this example 2024-09-30 20:08:39 -04:00
Greg Johnston
2b70961110 change: rename island-router feature so people don't use it 2024-09-30 20:08:24 -04:00
55 changed files with 183 additions and 211 deletions

View File

@@ -1,7 +1,7 @@
use counter::*;
use leptos::mount::mount_to;
use leptos::prelude::*;
use leptos::spawn::tick;
use leptos::task::tick;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -1,4 +1,4 @@
use leptos::{prelude::*, reactive_graph::actions::Action};
use leptos::prelude::*;
use leptos_router::{
components::{FlatRoutes, Route, Router, A},
StaticSegment,

View File

@@ -1,5 +1,5 @@
use counter_without_macros::counter;
use leptos::{prelude::*, spawn::tick};
use leptos::{prelude::*, task::tick};
use pretty_assertions::assert_eq;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -4,7 +4,7 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use counters::Counters;
use leptos::prelude::*;
use leptos::spawn::tick;
use leptos::task::tick;
use web_sys::HtmlElement;
#[wasm_bindgen_test]

View File

@@ -1,5 +1,5 @@
use directives::App;
use leptos::{prelude::*, spawn::tick};
use leptos::{prelude::*, task::tick};
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
use web_sys::HtmlElement;

View File

@@ -17,7 +17,7 @@ leptos = { path = "../../leptos", features = [
leptos_router = { path = "../../router" }
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
leptos_axum = { path = "../../integrations/axum", features = [
"islands-router",
"dont-use-islands-router",
], optional = true }
log = "0.4.22"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,19 +1,14 @@
# Leptos Todo App Sqlite with Axum
# Work in Progress
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server.
This example is something I wrote on a long layover in the Orlando airport in July. (It was really hot!)
## Getting Started
It is the culmination of a couple years of thinking and working toward being able to do this, which you can see
described pretty well in the pinned roadmap issue (#1830) and its discussion of different modes of client-side
routing when you use islands.
See the [Examples README](../README.md) for setup and run instructions.
This uses *only* server rendering, with no actual islands, but still maintains client-side state across page navigations.
It does this by building on the fact that we now have a statically-typed view tree to do pretty smart updates with
new HTML from the client, with extremely minimal diffing.
## E2E Testing
See the [E2E README](./e2e/README.md) for more information about the testing strategy.
## Rendering
See the [SSR Notes](../SSR_NOTES.md) for more information about Server Side Rendering.
## Quick Start
Run `cargo leptos watch` to run this example.
The demo itself works, but the feature that supports it is incomplete. A couple people have accidentally
used it and broken their applications in ways they don't understand, so I've renamed the feature to `dont-use-islands-router`.

View File

@@ -1,5 +1,5 @@
use js_framework_benchmark_leptos::*;
use leptos::{prelude::*, spawn::tick};
use leptos::{prelude::*, task::tick};
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;

View File

@@ -3,7 +3,7 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
use leptos::spawn::tick;
use leptos::task::tick;
use leptos::{leptos_dom::helpers::document, mount::mount_to};
use web_sys::HtmlButtonElement;

View File

@@ -1,6 +1,6 @@
use futures::StreamExt;
use http::Method;
use leptos::{html::Input, prelude::*, spawn::spawn_local};
use leptos::{html::Input, prelude::*, task::spawn_local};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use server_fn::{
client::{browser::BrowserClient, Client},

View File

@@ -33,7 +33,7 @@ once_cell = "1"
rustdoc-args = ["--generate-link-to-definition"]
[features]
islands-router = []
dont-use-islands-router = []
tracing = ["dep:tracing"]
[package.metadata.cargo-all-features]

View File

@@ -24,7 +24,7 @@ use leptos::{
config::LeptosOptions,
context::{provide_context, use_context},
prelude::expect_context,
reactive_graph::{computed::ScopedFuture, owner::Owner},
reactive::{computed::ScopedFuture, owner::Owner},
IntoView,
};
use leptos_integration_utils::{
@@ -749,7 +749,7 @@ where
IV: IntoView + 'static,
{
Box::pin(async move {
let app = if cfg!(feature = "islands-router") {
let app = if cfg!(feature = "dont-use-islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()

View File

@@ -39,7 +39,7 @@ tokio = { version = "1.39", features = ["net", "rt-multi-thread"] }
[features]
wasm = []
default = ["tokio/fs", "tokio/sync", "tower-http/fs", "tower/util"]
islands-router = []
dont-use-islands-router = []
tracing = ["dep:tracing"]
[package.metadata.docs.rs]

View File

@@ -53,7 +53,7 @@ use leptos::{
config::LeptosOptions,
context::{provide_context, use_context},
prelude::*,
reactive_graph::{computed::ScopedFuture, owner::Owner},
reactive::{computed::ScopedFuture, owner::Owner},
IntoView,
};
use leptos_integration_utils::{
@@ -784,7 +784,7 @@ where
_ = replace_blocks; // TODO
handle_response(additional_context, app_fn, |app, chunks| {
Box::pin(async move {
let app = if cfg!(feature = "islands-router") {
let app = if cfg!(feature = "dont-use-islands-router") {
app.to_html_stream_out_of_order_branching()
} else {
app.to_html_stream_out_of_order()
@@ -849,7 +849,7 @@ where
IV: IntoView + 'static,
{
handle_response(additional_context, app_fn, |app, chunks| {
let app = if cfg!(feature = "islands-router") {
let app = if cfg!(feature = "dont-use-islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()
@@ -1069,7 +1069,7 @@ where
{
handle_response(additional_context, app_fn, |app, chunks| {
Box::pin(async move {
let app = if cfg!(feature = "islands-router") {
let app = if cfg!(feature = "dont-use-islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()
@@ -1146,7 +1146,7 @@ where
IV: IntoView + 'static,
{
Box::pin(async move {
let app = if cfg!(feature = "islands-router") {
let app = if cfg!(feature = "dont-use-islands-router") {
app.to_html_stream_in_order_branching()
} else {
app.to_html_stream_in_order()

View File

@@ -2,7 +2,7 @@ use futures::{stream::once, Stream, StreamExt};
use hydration_context::{SharedContext, SsrSharedContext};
use leptos::{
nonce::use_nonce,
reactive_graph::owner::{Owner, Sandboxed},
reactive::owner::{Owner, Sandboxed},
IntoView,
};
use leptos_config::LeptosOptions;

View File

@@ -168,12 +168,11 @@ pub mod prelude {
pub use leptos_server::*;
pub use oco_ref::*;
pub use reactive_graph::{
actions::*, computed::*, effect::*, owner::*, signal::*, untrack,
wrappers::read::*,
actions::*, computed::*, effect::*, graph::untrack, owner::*,
signal::*, wrappers::read::*,
};
pub use server_fn::{self, ServerFnError};
pub use tachys::{
self,
reactive_graph::{bind::BindAttribute, node_ref::*, Suspend},
view::template::ViewTemplate,
};
@@ -230,6 +229,7 @@ mod suspense_component;
pub mod text_prop;
mod transition;
pub use leptos_macro::*;
#[doc(inline)]
pub use server_fn;
#[doc(hidden)]
pub use typed_builder;
@@ -237,16 +237,22 @@ pub use typed_builder;
pub use typed_builder_macro;
mod into_view;
pub use into_view::IntoView;
#[doc(inline)]
pub use leptos_dom;
mod provider;
#[doc(inline)]
pub use tachys;
/// Tools to mount an application to the DOM, or to hydrate it from server-rendered HTML.
pub mod mount;
#[doc(inline)]
pub use leptos_config as config;
#[doc(inline)]
pub use oco_ref as oco;
mod from_form_data;
#[doc(inline)]
pub use either_of as either;
pub use reactive_graph;
#[doc(inline)]
pub use reactive_graph as reactive;
/// Provide and access data along the reactive graph, sharing data without directly passing arguments.
pub mod context {
@@ -254,17 +260,22 @@ pub mod context {
pub use reactive_graph::owner::{provide_context, use_context};
}
#[doc(inline)]
pub use leptos_server as server;
/// HTML attribute types.
#[doc(inline)]
pub use tachys::html::attribute as attr;
/// HTML element types.
#[doc(inline)]
pub use tachys::html::element as html;
/// HTML event types.
#[doc(no_inline)]
pub use tachys::html::event as ev;
/// MathML element types.
#[doc(inline)]
pub use tachys::mathml as math;
/// SVG element types.
#[doc(inline)]
pub use tachys::svg;
/// Utilities for simple isomorphic logging to the console or terminal.
@@ -272,7 +283,7 @@ pub mod logging {
pub use leptos_dom::{debug_warn, error, log, warn};
}
pub mod spawn {
pub mod task {
pub use any_spawner::Executor;
use std::future::Future;
@@ -290,6 +301,7 @@ pub mod spawn {
Executor::spawn_local(fut)
}
/// Waits until the next "tick" of the current async executor.
pub async fn tick() {
Executor::tick().await
}

View File

@@ -1,7 +1,7 @@
use crate::{children::TypedChildrenFn, mount, IntoView};
use leptos_dom::helpers::document;
use leptos_macro::component;
use reactive_graph::{effect::Effect, owner::Owner, untrack};
use reactive_graph::{effect::Effect, graph::untrack, owner::Owner};
use std::sync::Arc;
/// Renders components somewhere else in the DOM.

View File

@@ -255,7 +255,7 @@ impl ToTokens for Model {
let body_name = unmodified_fn_name_from_fn_name(&body_name);
let body_expr = if is_island {
quote! {
::leptos::reactive_graph::owner::Owner::with_hydration(move || {
::leptos::reactive::owner::Owner::with_hydration(move || {
#body_name(#prop_names)
})
}
@@ -266,7 +266,7 @@ impl ToTokens for Model {
};
let component = quote! {
::leptos::reactive_graph::untrack(
::leptos::prelude::untrack(
move || {
#tracing_guard_expr
#tracing_props_expr
@@ -280,7 +280,7 @@ impl ToTokens for Model {
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
quote! {
{
if ::leptos::reactive_graph::owner::Owner::current_shared_context()
if ::leptos::reactive::owner::Owner::current_shared_context()
.map(|sc| sc.get_is_hydrating())
.unwrap_or(false) {
::leptos::either::Either::Left(
@@ -316,9 +316,9 @@ impl ToTokens for Model {
quote! {
use leptos::tachys::view::any_view::IntoAny;
let children = Box::new(|| {
let sc = ::leptos::reactive_graph::owner::Owner::current_shared_context().unwrap();
let sc = ::leptos::reactive::owner::Owner::current_shared_context().unwrap();
let prev = sc.get_is_hydrating();
let value = ::leptos::reactive_graph::owner::Owner::with_no_hydration(||
let value = ::leptos::reactive::owner::Owner::with_no_hydration(||
::leptos::tachys::html::islands::IslandChildren::new(children()).into_any()
);
sc.set_is_hydrating(prev);

View File

@@ -40,9 +40,9 @@ impl ToTokens for MemoMacroInput {
let path = &self.path;
tokens.extend(quote! {
::leptos::reactive_graph::computed::Memo::new(
::leptos::reactive::computed::Memo::new(
move |_| {
use ::leptos::reactive_graph::traits::With;
use ::leptos::reactive::traits::With;
#root.with(|st: _| st.#path.clone())
}
)

View File

@@ -40,7 +40,7 @@ impl ToTokens for SliceMacroInput {
let path = &self.path;
tokens.extend(quote! {
::leptos::reactive_graph::computed::create_slice(
::leptos::reactive::computed::create_slice(
#root,
|st: &_| st.#path.clone(),
|st: &mut _, n| st.#path = n

View File

@@ -307,10 +307,12 @@ fn inert_element_to_tokens(
match current {
Node::RawText(raw) => {
let text = raw.to_string_best();
let text = html_escape::encode_text(&text);
html.push_str(&text);
}
Node::Text(text) => {
let text = text.value_string();
let text = html_escape::encode_text(&text);
html.push_str(&text);
}
Node::Element(node) => {
@@ -337,11 +339,13 @@ fn inert_element_to_tokens(
)) = &value.value
{
if let Lit::Str(txt) = &lit.lit {
let value = txt.value();
let value = html_escape::encode_double_quoted_attribute(&value);
if attr_name == "class" {
html.push_class(&txt.value());
html.push_class(&value);
} else {
html.push_str("=\"");
html.push_str(&txt.value());
html.push_str(&value);
html.push('"');
}
}

View File

@@ -2,7 +2,7 @@ use crate::ServerMetaContext;
use leptos::{
attr::NextAttribute,
component, html,
reactive_graph::owner::use_context,
reactive::owner::use_context,
tachys::{
dom::document,
html::attribute::Attribute,

View File

@@ -2,7 +2,7 @@ use crate::ServerMetaContext;
use leptos::{
attr::NextAttribute,
component, html,
reactive_graph::owner::use_context,
reactive::owner::use_context,
tachys::{
dom::document,
html::attribute::Attribute,

View File

@@ -52,7 +52,7 @@ use leptos::{
attr::NextAttribute,
component,
logging::debug_warn,
reactive_graph::owner::{provide_context, use_context},
reactive::owner::{provide_context, use_context},
tachys::{
dom::document,
html::{

View File

@@ -3,7 +3,7 @@ use leptos::{
attr::Attribute,
component,
oco::Oco,
reactive_graph::{
reactive::{
effect::RenderEffect,
owner::{use_context, Owner},
},

View File

@@ -30,23 +30,21 @@ sqlx = { version = "0.8.0", features = [
], optional = true }
thiserror = "1.0"
wasm-bindgen = "0.2.0"
axum_session_auth = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
axum_session = { version = "0.14.0", features = [
"sqlite-rustls",
], optional = true }
axum_session_auth = { version = "0.14.0", features = [], optional = true }
axum_session = { version = "0.14.0", features = [], optional = true }
axum_session_sqlx = { version = "0.3.0", features = [ "sqlite", "tls-rustls"], optional = true }
bcrypt = { version = "0.15.0", optional = true }
async-trait = { version = "0.1.0", optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
hydrate = ["leptos/hydrate"]
ssr = [
"dep:axum",
"dep:tower",
"dep:tower-http",
"dep:tokio",
"dep:axum_session_sqlx",
"dep:axum_session_auth",
"dep:axum_session",
"dep:async-trait",

View File

@@ -28,9 +28,8 @@ impl Default for User {
#[cfg(feature = "ssr")]
pub mod ssr {
pub use super::{User, UserPasshash};
pub use axum_session_auth::{
Authentication, HasPermission, SessionSqlitePool,
};
pub use axum_session_auth::{Authentication, HasPermission};
use axum_session_sqlx::SessionSqlitePool;
pub use sqlx::SqlitePool;
pub use std::collections::HashSet;
pub type AuthSession = axum_session_auth::AuthSession<

View File

@@ -8,10 +8,10 @@ use leptos_axum::ResponseOptions;
#[component]
pub fn ErrorTemplate(
#[prop(optional)] outside_errors: Option<Errors>,
#[prop(optional)] errors: Option<RwSignal<Errors>>,
#[prop(optional)] errors: Option<ArcRwSignal<Errors>>,
) -> impl IntoView {
let errors = match outside_errors {
Some(e) => RwSignal::new(e),
Some(e) => ArcRwSignal::new(e),
None => match errors {
Some(e) => e,
None => panic!("No Errors found and we expected errors!"),

View File

@@ -1,50 +0,0 @@
use crate::{error_template::ErrorTemplate, errors::TodoAppError};
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, Errors, LeptosOptions};
use tower::ServiceExt;
use tower_http::services::ServeDir;
pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
let res = get_static_file(uri.clone(), &root).await.unwrap();
if res.status() == StatusCode::OK {
res.into_response()
} else {
let mut errors = Errors::default();
errors.insert_with_default_key(TodoAppError::NotFound);
let handler = leptos_axum::render_app_to_stream(
options.to_owned(),
move || view! {<ErrorTemplate outside_errors=errors.clone()/>},
);
handler(req).await.into_response()
}
}
async fn get_static_file(
uri: Uri,
root: &str,
) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)),
}
}

View File

@@ -2,8 +2,6 @@ pub mod auth;
pub mod error_template;
pub mod errors;
#[cfg(feature = "ssr")]
pub mod fallback;
#[cfg(feature = "ssr")]
pub mod state;
pub mod todo;
@@ -14,5 +12,5 @@ pub fn hydrate() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount_to_body(TodoApp);
leptos::mount::hydrate_body(TodoApp);
}

View File

@@ -7,14 +7,16 @@ use axum::{
Router,
};
use axum_session::{SessionConfig, SessionLayer, SessionStore};
use axum_session_auth::{AuthConfig, AuthSessionLayer, SessionSqlitePool};
use leptos::{get_configuration, logging::log, provide_context};
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_sqlx::SessionSqlitePool;
use leptos::{
config::get_configuration, logging::log, prelude::provide_context,
};
use leptos_axum::{
generate_route_list, handle_server_fns_with_context, LeptosRoutes,
};
use session_auth_axum::{
auth::{ssr::AuthSession, User},
fallback::file_and_error_handler,
state::AppState,
todo::*,
};
@@ -40,19 +42,19 @@ async fn server_fn_handler(
async fn leptos_routes_handler(
auth_session: AuthSession,
State(app_state): State<AppState>,
state: State<AppState>,
req: Request<AxumBody>,
) -> Response {
let State(app_state) = state.clone();
let handler = leptos_axum::render_route_with_context(
app_state.leptos_options.clone(),
app_state.routes.clone(),
move || {
provide_context(auth_session.clone());
provide_context(app_state.pool.clone());
},
TodoApp,
move || shell(app_state.leptos_options.clone()),
);
handler(req).await.into_response()
handler(state, req).await.into_response()
}
#[tokio::main]
@@ -111,7 +113,7 @@ async fn main() {
get(server_fn_handler).post(server_fn_handler),
)
.leptos_routes_with_handler(routes, get(leptos_routes_handler))
.fallback(file_and_error_handler)
.fallback(leptos_axum::file_and_error_handler::<AppState, _>(shell))
.layer(
AuthSessionLayer::<User, i64, SessionSqlitePool, SqlitePool>::new(
Some(pool.clone()),

View File

@@ -1,6 +1,6 @@
use axum::extract::FromRef;
use leptos::LeptosOptions;
use leptos_router::RouteListing;
use leptos::prelude::LeptosOptions;
use leptos_axum::AxumRouteListing;
use sqlx::SqlitePool;
/// This takes advantage of Axum's SubStates feature by deriving FromRef. This is the only way to have more than one
@@ -9,5 +9,5 @@ use sqlx::SqlitePool;
pub struct AppState {
pub leptos_options: LeptosOptions,
pub pool: SqlitePool,
pub routes: Vec<RouteListing>,
pub routes: Vec<AxumRouteListing>,
}

View File

@@ -1,7 +1,7 @@
use crate::{auth::*, error_template::ErrorTemplate};
use leptos::prelude::*;
use leptos_meta::*;
use leptos_router::*;
use leptos_router::{components::*, *};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -109,13 +109,33 @@ pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
.map(|_| ())?)
}
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<AutoReload options=options.clone() />
<HydrationScripts options/>
<link rel="stylesheet" id="leptos" href="/pkg/session_auth_axum.css"/>
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
<MetaTags/>
</head>
<body>
<TodoApp/>
</body>
</html>
}
}
#[component]
pub fn TodoApp() -> impl IntoView {
let login = create_server_action::<Login>();
let logout = create_server_action::<Logout>();
let signup = create_server_action::<Signup>();
let login = ServerAction::<Login>::new();
let logout = ServerAction::<Logout>::new();
let signup = ServerAction::<Signup>::new();
let user = create_resource(
let user = Resource::new(
move || {
(
login.version().get(),
@@ -128,8 +148,6 @@ pub fn TodoApp() -> impl IntoView {
provide_meta_context();
view! {
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Stylesheet id="leptos" href="/pkg/session_auth_axum.css"/>
<Router>
<header>
<A href="/">
@@ -149,7 +167,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>{format!("Login error: {}", e)}</span>
}
.into_view()
.into_any()
}
Ok(None) => {
view! {
@@ -159,7 +177,7 @@ pub fn TodoApp() -> impl IntoView {
", "
<span>"Logged out."</span>
}
.into_view()
.into_any()
}
Ok(Some(user)) => {
view! {
@@ -169,7 +187,7 @@ pub fn TodoApp() -> impl IntoView {
{format!("Logged in as: {} ({})", user.username, user.id)}
</span>
}
.into_view()
.into_any()
}
})
}}
@@ -178,13 +196,15 @@ pub fn TodoApp() -> impl IntoView {
</header>
<hr/>
<main>
<Routes>
<FlatRoutes fallback=|| "Not found.">
// Route
<Route path="" view=Todos/>
<Route path="signup" view=move || view! { <Signup action=signup/> }/>
<Route path="login" view=move || view! { <Login action=login/> }/>
<Route
path="settings"
<Route path=path!("") view=Todos/>
<Route path=path!("signup") view=move || view! { <Signup action=signup/> }/>
<Route path=path!("login") view=move || view! { <Login action=login/> }/>
<ProtectedRoute
path=path!("settings")
condition=move || user.get().map(|r| r.ok().flatten().is_some())
redirect_path=|| "/"
view=move || {
view! {
<h1>"Settings"</h1>
@@ -193,7 +213,7 @@ pub fn TodoApp() -> impl IntoView {
}
/>
</Routes>
</FlatRoutes>
</main>
</Router>
}
@@ -201,12 +221,12 @@ pub fn TodoApp() -> impl IntoView {
#[component]
pub fn Todos() -> impl IntoView {
let add_todo = create_server_multi_action::<AddTodo>();
let delete_todo = create_server_action::<DeleteTodo>();
let add_todo = ServerMultiAction::<AddTodo>::new();
let delete_todo = ServerAction::<DeleteTodo>::new();
let submissions = add_todo.submissions();
// list of todos is loaded from the server in reaction to changes
let todos = create_resource(
let todos = Resource::new(
move || (add_todo.version().get(), delete_todo.version().get()),
move |_| get_todos(),
);
@@ -231,11 +251,11 @@ pub fn Todos() -> impl IntoView {
view! {
<pre class="error">"Server Error: " {e.to_string()}</pre>
}
.into_view()
.into_any()
}
Ok(todos) => {
if todos.is_empty() {
view! { <p>"No tasks were found."</p> }.into_view()
view! { <p>"No tasks were found."</p> }.into_any()
} else {
todos
.into_iter()
@@ -252,10 +272,11 @@ pub fn Todos() -> impl IntoView {
}
})
.collect_view()
.into_any()
}
}
})
.unwrap_or_default()
.unwrap_or(().into_any())
}
};
let pending_todos = move || {
@@ -266,7 +287,7 @@ pub fn Todos() -> impl IntoView {
.map(|submission| {
view! {
<li class="pending">
{move || submission.input.get().map(|data| data.title)}
{move || submission.input().get().map(|data| data.title)}
</li>
}
})
@@ -282,9 +303,7 @@ pub fn Todos() -> impl IntoView {
}
#[component]
pub fn Login(
action: Action<Login, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Login(action: ServerAction<Login>) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Log In"</h1>
@@ -317,9 +336,7 @@ pub fn Login(
}
#[component]
pub fn Signup(
action: Action<Signup, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Signup(action: ServerAction<Signup>) -> impl IntoView {
view! {
<ActionForm action=action>
<h1>"Sign Up"</h1>
@@ -362,9 +379,7 @@ pub fn Signup(
}
#[component]
pub fn Logout(
action: Action<Logout, Result<(), ServerFnError>>,
) -> impl IntoView {
pub fn Logout(action: ServerAction<Logout>) -> impl IntoView {
view! {
<div id="loginbox">
<ActionForm action=action>

View File

@@ -19,7 +19,7 @@ use crate::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
transition::AsyncTransition,
};
@@ -600,7 +600,7 @@ impl<T: 'static> Notify for ArcAsyncDerived<T> {
}
}
impl<T: 'static> Writeable for ArcAsyncDerived<T> {
impl<T: 'static> Write for ArcAsyncDerived<T> {
type Value = Option<T>;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -8,7 +8,7 @@ use crate::{
signal::guards::{AsyncPlain, ReadGuard, WriteGuard},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
UntrackableGuard, Writeable,
UntrackableGuard, Write,
},
unwrap_signal,
};
@@ -300,7 +300,7 @@ where
}
}
impl<T, S> Writeable for AsyncDerived<T, S>
impl<T, S> Write for AsyncDerived<T, S>
where
T: 'static,
S: Storage<ArcAsyncDerived<T>>,

View File

@@ -58,7 +58,7 @@ impl<Fut: Future> Future for ScopedFuture<Fut> {
pub mod suspense {
use crate::{
signal::ArcRwSignal,
traits::{Update, Writeable},
traits::{Update, Write},
};
use futures::channel::oneshot::Sender;
use or_poisoned::OrPoisoned;

View File

@@ -106,7 +106,7 @@ impl Observer {
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::untrack;
/// # use reactive_graph::graph::untrack;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (a, set_a) = signal(0);

View File

@@ -89,7 +89,6 @@ pub mod transition;
pub mod wrappers;
use computed::ScopedFuture;
pub use graph::untrack;
#[cfg(feature = "nightly")]
mod nightly;

View File

@@ -6,7 +6,7 @@ use super::{
use crate::{
graph::{ReactiveNode, SubscriberSet},
prelude::{IsDisposed, Notify},
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Writeable},
traits::{DefinedAt, ReadUntracked, UntrackableGuard, Write},
};
use core::fmt::{Debug, Formatter, Result};
use std::{
@@ -50,7 +50,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -253,7 +253,7 @@ impl<T> Notify for ArcRwSignal<T> {
}
}
impl<T: 'static> Writeable for ArcRwSignal<T> {
impl<T: 'static> Write for ArcRwSignal<T> {
type Value = T;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -2,7 +2,7 @@ use super::guards::{UntrackedWriteGuard, WriteGuard};
use crate::{
graph::{ReactiveNode, SubscriberSet},
prelude::{IsDisposed, Notify},
traits::{DefinedAt, UntrackableGuard, Writeable},
traits::{DefinedAt, UntrackableGuard, Write},
};
use core::fmt::{Debug, Formatter, Result};
use std::{
@@ -23,7 +23,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -122,7 +122,7 @@ impl<T> Notify for ArcWriteSignal<T> {
}
}
impl<T: 'static> Writeable for ArcWriteSignal<T> {
impl<T: 'static> Write for ArcWriteSignal<T> {
type Value = T;
fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {

View File

@@ -9,7 +9,7 @@ use crate::{
signal::guards::{UntrackedWriteGuard, WriteGuard},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked,
UntrackableGuard, Writeable,
UntrackableGuard, Write,
},
unwrap_signal,
};
@@ -57,7 +57,7 @@ use std::{
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -349,7 +349,7 @@ where
}
}
impl<T, S> Writeable for RwSignal<T, S>
impl<T, S> Write for RwSignal<T, S>
where
T: 'static,
S: Storage<ArcRwSignal<T>>,

View File

@@ -1,9 +1,7 @@
use super::{guards::WriteGuard, ArcWriteSignal};
use crate::{
owner::{ArenaItem, Storage, SyncStorage},
traits::{
DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Writeable,
},
traits::{DefinedAt, Dispose, IsDisposed, Notify, UntrackableGuard, Write},
};
use core::fmt::Debug;
use guardian::ArcRwLockWriteGuardian;
@@ -22,7 +20,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
/// - [`.set()`](crate::traits::Set) sets the signal to a new value.
/// - [`.update()`](crate::traits::Update) updates the value of the signal by
/// applying a closure that takes a mutable reference.
/// - [`.write()`](crate::traits::Writeable) returns a guard through which the signal
/// - [`.write()`](crate::traits::Write) returns a guard through which the signal
/// can be mutated, and which notifies subscribers when it is dropped.
///
/// > Each of these has a related `_untracked()` method, which updates the signal
@@ -128,7 +126,7 @@ where
}
}
impl<T, S> Writeable for WriteSignal<T, S>
impl<T, S> Write for WriteSignal<T, S>
where
T: 'static,
S: Storage<ArcWriteSignal<T>>,

View File

@@ -18,7 +18,7 @@
//! | [`Track`] | — | Tracks changes to this value, adding it as a source of the current reactive observer. |
//! | [`Trigger`] | — | Notifies subscribers that this value has changed. |
//! | [`ReadUntracked`] | Guard | Gives immutable access to the value of this signal. |
//! | [`Writeable`] | Guard | Gives mutable access to the value of this signal.
//! | [`Write`] | Guard | Gives mutable access to the value of this signal.
//!
//! ## Derived Traits
//!
@@ -33,7 +33,7 @@
//! ### Update
//! | Trait | Mode | Composition | Description
//! |---------------------|---------------|-----------------------------------|------------
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Writeable`] | Applies closure to the current value to update it, but doesn't notify subscribers.
//! | [`UpdateUntracked`] | `fn(&mut T)` | [`Write`] | Applies closure to the current value to update it, but doesn't notify subscribers.
//! | [`Update`] | `fn(&mut T)` | [`UpdateUntracked`] + [`Trigger`] | Applies closure to the current value to update it, and notifies subscribers.
//! | [`Set`] | `T` | [`Update`] | Sets the value to a new value, and notifies subscribers.
//!
@@ -61,6 +61,7 @@ use std::{
panic::Location,
};
#[doc(hidden)]
/// Provides a sensible panic message for accessing disposed signals.
#[macro_export]
macro_rules! unwrap_signal {
@@ -213,7 +214,7 @@ pub trait UntrackableGuard: DerefMut {
/// Gives mutable access to a signal's value through a guard type. When the guard is dropped, the
/// signal's subscribers will be notified.
pub trait Writeable: Sized + DefinedAt + Notify {
pub trait Write: Sized + DefinedAt + Notify {
/// The type of the signal's value.
type Value: Sized + 'static;
@@ -421,9 +422,9 @@ pub trait UpdateUntracked: DefinedAt {
impl<T> UpdateUntracked for T
where
T: Writeable,
T: Write,
{
type Value = <Self as Writeable>::Value;
type Value = <Self as Write>::Value;
#[track_caller]
fn try_update_untracked<U>(
@@ -478,9 +479,9 @@ pub trait Update {
impl<T> Update for T
where
T: Writeable,
T: Write,
{
type Value = <Self as Writeable>::Value;
type Value = <Self as Write>::Value;
#[track_caller]
fn try_maybe_update<U>(

View File

@@ -4,10 +4,11 @@
pub mod read {
use crate::{
computed::{ArcMemo, Memo},
graph::untrack,
owner::{ArenaItem, FromLocal, LocalStorage, Storage, SyncStorage},
signal::{ArcReadSignal, ArcRwSignal, ReadSignal, RwSignal},
traits::{DefinedAt, Dispose, Get, With, WithUntracked},
untrack, unwrap_signal,
unwrap_signal,
};
use send_wrapper::SendWrapper;
use std::{panic::Location, sync::Arc};

View File

@@ -3,7 +3,7 @@ use reactive_graph::{
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
traits::{
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
WithUntracked, Writeable,
WithUntracked, Write,
},
};

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
};
use std::{
@@ -171,7 +171,7 @@ where
}
}
impl<Inner, Prev> Writeable for AtIndex<Inner, Prev>
impl<Inner, Prev> Write for AtIndex<Inner, Prev>
where
Inner: StoreField<Value = Prev>,
Prev: IndexMut<usize> + 'static,

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
};
use std::{
@@ -312,7 +312,7 @@ where
}
}
impl<Inner, Prev, K, T> Writeable for KeyedSubfield<Inner, Prev, K, T>
impl<Inner, Prev, K, T> Write for KeyedSubfield<Inner, Prev, K, T>
where
Self: Clone,
for<'a> &'a T: IntoIterator,
@@ -574,7 +574,7 @@ where
}
}
impl<Inner, Prev, K, T> Writeable for AtKeyed<Inner, Prev, K, T>
impl<Inner, Prev, K, T> Write for AtKeyed<Inner, Prev, K, T>
where
K: Debug + Send + Sync + PartialEq + Eq + Hash + 'static,
KeyedSubfield<Inner, Prev, K, T>: Clone,

View File

@@ -7,7 +7,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
};
use rustc_hash::FxHashMap;
@@ -254,7 +254,7 @@ where
}
}
impl<T> Writeable for ArcStore<T>
impl<T> Write for ArcStore<T>
where
T: 'static,
{
@@ -379,7 +379,7 @@ where
}
}
impl<T, S> Writeable for Store<T, S>
impl<T, S> Write for Store<T, S>
where
T: 'static,
S: Storage<ArcStore<T>>,
@@ -428,7 +428,7 @@ mod tests {
use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
use reactive_graph::{
effect::Effect,
traits::{Read, ReadUntracked, Set, Update, Writeable},
traits::{Read, ReadUntracked, Set, Update, Write},
};
use reactive_stores_macro::{Patch, Store};
use std::sync::{

View File

@@ -49,7 +49,7 @@ mod tests {
use crate::{self as reactive_stores, Store};
use reactive_graph::{
effect::Effect,
traits::{Get, Read, ReadUntracked, Set, Writeable},
traits::{Get, Read, ReadUntracked, Set, Write},
};
use reactive_stores_macro::Store;
use std::sync::{

View File

@@ -11,7 +11,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
unwrap_signal,
};
@@ -251,7 +251,7 @@ where
}
}
impl<T, S> Writeable for Then<T, S>
impl<T, S> Write for Then<T, S>
where
T: 'static,
S: StoreField,

View File

@@ -10,7 +10,7 @@ use reactive_graph::{
},
traits::{
DefinedAt, IsDisposed, Notify, ReadUntracked, Track, UntrackableGuard,
Writeable,
Write,
},
};
use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
@@ -168,7 +168,7 @@ where
}
}
impl<Inner, Prev, T> Writeable for Subfield<Inner, Prev, T>
impl<Inner, Prev, T> Write for Subfield<Inner, Prev, T>
where
T: 'static,
Inner: StoreField<Value = Prev>,

View File

@@ -4,7 +4,7 @@ use crate::{
location::{BrowserUrl, LocationProvider},
NavigateOptions,
};
use leptos::{ev, html::form, prelude::*, spawn::spawn_local};
use leptos::{ev, html::form, prelude::*, task::spawn_local};
use std::{error::Error, sync::Arc};
use wasm_bindgen::{JsCast, UnwrapThrowExt};
use web_sys::{FormData, RequestRedirect, Response};

View File

@@ -1,5 +1,5 @@
use crate::{components::RouterContext, hooks::use_resolved_path};
use leptos::{children::Children, oco::Oco, prelude::*, *};
use leptos::{children::Children, oco::Oco, prelude::*};
use reactive_graph::{computed::ArcMemo, owner::use_context};
use std::{borrow::Cow, rc::Rc};

View File

@@ -1,6 +1,6 @@
use crate::{hooks::RawParamsMap, params::ParamsMap, PathSegment};
use futures::{channel::oneshot, stream, Stream, StreamExt};
use leptos::spawn::spawn;
use leptos::task::spawn;
use reactive_graph::{owner::Owner, traits::GetUntracked};
use std::{
fmt::{Debug, Display},

View File

@@ -41,7 +41,7 @@ where
///
/// Example:
///
/// ```
/// ```ignore
/// // You can use `RwSignal`s
/// let is_awesome = RwSignal::new(true);
///