Fixing merge issues

This commit is contained in:
Greg Johnston 2022-12-28 14:51:01 -05:00
parent f09ded454d
commit fd2a2bd5f4
14 changed files with 2 additions and 581 deletions

View file

@ -3,7 +3,6 @@ members = [
# core
"leptos",
"leptos_dom",
"leptos_core",
"leptos_config",
"leptos_macro",
"leptos_reactive",

View file

@ -1,48 +0,0 @@
[package]
name = "todo-app-cbor"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
actix-files = { version = "0.6", optional = true }
actix-web = { version = "4", optional = true, features = ["openssl", "macros"] }
anyhow = "1"
broadcaster = "1"
console_log = "0.2"
console_error_panic_hook = "0.1"
serde = { version = "1", features = ["derive"] }
futures = "0.3"
cfg-if = "1"
leptos = { path = "../../leptos", default-features = false, features = [
"serde",
] }
leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_meta = { path = "../../meta", default-features = false }
leptos_router = { path = "../../router", default-features = false }
log = "0.4"
simple_logger = "2"
gloo = { git = "https://github.com/rustwasm/gloo" }
sqlx = { version = "0.6", features = [
"runtime-tokio-rustls",
"sqlite",
], optional = true }
[features]
default = ["ssr"]
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
ssr = [
"dep:actix-files",
"dep:actix-web",
"dep:sqlx",
"leptos/ssr",
"leptos_actix",
"leptos_meta/ssr",
"leptos_router/ssr",
]
[package.metadata.cargo-all-features]
denylist = ["actix-files", "actix-web", "leptos_actix", "sqlx"]
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Greg Johnston
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,22 +0,0 @@
# Leptos Todo App Sqlite with CBOR
This example creates a basic todo app with an Actix backend that uses Leptos' server functions to call sqlx from the client and seamlessly run it on the server. It is identical to the todo-app-sqlite example, but utilizes CBOR encoding for one of the server functions
## Server Side Rendering With Hydration
To run it as a server side app with hydration, first you should run
```bash
wasm-pack build --target=web --no-default-features --features=hydrate
```
to generate the WebAssembly to hydrate the HTML that is generated on the server.
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
```bash
cargo run --no-default-features --features=ssr
```
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!

Binary file not shown.

View file

@ -1,6 +0,0 @@
CREATE TABLE IF NOT EXISTS todos
(
id INTEGER NOT NULL PRIMARY KEY,
title VARCHAR,
completed BOOLEAN
);

View file

@ -1,22 +0,0 @@
use cfg_if::cfg_if;
pub mod todo;
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
cfg_if! {
if #[cfg(feature = "hydrate")] {
use leptos::*;
use wasm_bindgen::prelude::wasm_bindgen;
use crate::todo::*;
#[wasm_bindgen]
pub fn hydrate() {
console_error_panic_hook::set_once();
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::hydrate(body().unwrap(), |cx| {
view! { cx, <TodoApp/> }
});
}
}
}

View file

@ -1,49 +0,0 @@
use cfg_if::cfg_if;
use leptos::*;
mod todo;
// boilerplate to run in different modes
cfg_if! {
// server-only stuff
if #[cfg(feature = "ssr")] {
use actix_files::{Files};
use actix_web::*;
use crate::todo::*;
use std::{ net::SocketAddr,env };
#[get("/style.css")]
async fn css() -> impl Responder {
actix_files::NamedFile::open_async("./style.css").await
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut conn = db().await.expect("couldn't connect to DB");
sqlx::migrate!()
.run(&mut conn)
.await
.expect("could not run SQLx migrations");
crate::todo::register_server_functions();
let addr = SocketAddr::from(([127,0,0,1],3000));
HttpServer::new(move || {
let render_options: RenderOptions = RenderOptions::builder().pkg_path("/pkg/todo_app_cbor").reload_port(3001).socket_address(addr.clone()).environment(&env::var("RUST_ENV")).build();
render_options.write_to_file();
App::new()
.service(Files::new("/pkg", "./pkg"))
.service(css)
.route("/api/{tail:.*}", leptos_actix::handle_server_fns())
.route("/{tail:.*}", leptos_actix::render_app_to_stream(render_options, |cx| view! { cx, <TodoApp/> }))
//.wrap(middleware::Compress::default())
})
.bind(&addr)?
.run()
.await
}
} else {
fn main() {
// no client-side main function
}
}
}

View file

@ -1,212 +0,0 @@
use cfg_if::cfg_if;
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
use serde::{Deserialize, Serialize};
cfg_if! {
if #[cfg(feature = "ssr")] {
use sqlx::{Connection, SqliteConnection};
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
}
pub fn register_server_functions() {
_ = GetTodos::register();
_ = AddTodo::register();
_ = DeleteTodo::register();
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
} else {
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Todo {
id: u16,
title: String,
completed: bool,
}
}
}
#[server(GetTodos, "/api", "Url")]
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
// this is just an example of how to access server context injected in the handlers
let req =
use_context::<actix_web::HttpRequest>(cx).expect("couldn't get HttpRequest from context");
println!("req.path = {:?}", req.path());
use futures::TryStreamExt;
let mut conn = db().await?;
let mut todos = Vec::new();
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
while let Some(row) = rows
.try_next()
.await
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
{
todos.push(row);
}
Ok(todos)
}
#[server(AddTodo, "/api", "Cbor")]
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
let mut conn = db().await?;
// fake API delay
std::thread::sleep(std::time::Duration::from_millis(1250));
sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
.bind(title)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
}
#[server(DeleteTodo, "/api")]
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
let mut conn = db().await?;
sqlx::query("DELETE FROM todos WHERE id = $1")
.bind(id)
.execute(&mut conn)
.await
.map(|_| ())
.map_err(|e| ServerFnError::ServerError(e.to_string()))
}
#[component]
pub fn TodoApp(cx: Scope) -> Element {
view! {
cx,
<div>
<Stylesheet href="/style.css"/>
<Router>
<header>
<h1>"My Tasks"</h1>
</header>
<main>
<Routes>
<Route path="" element=|cx| view! {
cx,
<Todos/>
}/>
</Routes>
</main>
</Router>
</div>
}
}
#[component]
pub fn Todos(cx: Scope) -> Element {
let add_todo = create_server_multi_action::<AddTodo>(cx);
let delete_todo = create_server_action::<DeleteTodo>(cx);
let submissions = add_todo.submissions();
// track mutations that should lead us to refresh the list
let add_changed = add_todo.version;
let todo_deleted = delete_todo.version;
// list of todos is loaded from the server in reaction to changes
let todos = create_resource(
cx,
move || (add_changed(), todo_deleted()),
move |_| get_todos(cx),
);
view! {
cx,
<div>
<MultiActionForm action=add_todo>
<label>
"Add a Todo"
<input type="text" name="title"/>
</label>
<input type="submit" value="Add"/>
</MultiActionForm>
<div>
<Suspense fallback=view! {cx, <p>"Loading..."</p> }>
{
let delete_todo = delete_todo.clone();
move || {
let existing_todos = {
let delete_todo = delete_todo.clone();
move || {
todos
.read()
.map({
let delete_todo = delete_todo.clone();
move |todos| match todos {
Err(e) => {
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}]
}
Ok(todos) => {
if todos.is_empty() {
vec![view! { cx, <p>"No tasks were found."</p> }]
} else {
todos
.into_iter()
.map({
let delete_todo = delete_todo.clone();
move |todo| {
let delete_todo = delete_todo.clone();
view! {
cx,
<li>
{todo.title}
<ActionForm action=delete_todo.clone()>
<input type="hidden" name="id" value=todo.id/>
<input type="submit" value="X"/>
</ActionForm>
</li>
}
}
})
.collect::<Vec<_>>()
}
}
}
})
.unwrap_or_default()
}
};
let pending_todos = move || {
submissions
.get()
.into_iter()
.filter(|submission| submission.pending().get())
.map(|submission| {
view! {
cx,
<li class="pending">{move || submission.input.get().map(|data| data.title) }</li>
}
})
.collect::<Vec<_>>()
};
view! {
cx,
<ul>
<div>{existing_todos}</div>
<div>{pending_todos}</div>
</ul>
}
}
}
</Suspense>
</div>
</div>
}
}

View file

@ -1,3 +0,0 @@
.pending {
color: purple;
}

View file

@ -22,7 +22,7 @@ typed-builder = "0.11"
leptos = { path = ".", default-features = false }
[features]
default = ["csr", "serde", "interning"]
default = ["csr", "serde"]
csr = [
"leptos_dom/web",
"leptos_macro/csr",
@ -53,7 +53,7 @@ miniserde = ["leptos_reactive/miniserde"]
tracing = ["leptos_macro/tracing"]
[package.metadata.cargo-all-features]
denylist = ["stable", "interning"]
denylist = ["stable"]
skip_feature_sets = [
[
"csr",

View file

@ -1,12 +0,0 @@
use rustc_version::{version, version_meta, Channel};
fn main() {
assert!(version().unwrap().major >= 1);
match version_meta().unwrap().channel {
Channel::Stable => {
println!("cargo:rustc-cfg=feature=\"stable\"")
}
_ => {}
}
}

View file

@ -1,178 +0,0 @@
use leptos_dom::{Child, IntoChild};
use leptos_reactive::{provide_context, Scope, SignalSetter, SuspenseContext};
use typed_builder::TypedBuilder;
/// Props for the [Suspense](crate::Suspense) component, which shows a fallback
/// while [Resource](leptos_reactive::Resource)s are being read.
#[derive(TypedBuilder)]
pub struct TransitionProps<F, E, G>
where
F: IntoChild + Clone,
E: IntoChild,
G: Fn() -> E,
{
/// Will be displayed while resources are pending.
pub fallback: F,
/// A function that will be called when the component transitions into or out of
/// the `pending` state, with its argument indicating whether it is pending (`true`)
/// or not pending (`false`).
#[builder(default, setter(strip_option, into))]
pub set_pending: Option<SignalSetter<bool>>,
/// Will be displayed once all resources have resolved.
pub children: Box<dyn Fn() -> Vec<G>>,
}
/// If any [Resource](leptos_reactive::Resource)s are read in the `children` of this
/// component, it will show the `fallback` while they are loading. Once all are resolved,
/// it will render the `children`. Unlike [`Suspense`](crate::Suspense), this will not fall
/// back to the `fallback` state if there are further changes after the initial load.
///
/// Note that the `children` will be rendered initially (in order to capture the fact that
/// those resources are read under the suspense), so you cannot assume that resources have
/// `Some` value in `children`.
///
/// ```
/// # use leptos_reactive::*;
/// # use leptos_core::*;
/// # use leptos_macro::*;
/// # use leptos_dom::*; use leptos::*;
/// # run_scope(create_runtime(), |cx| {
/// # if cfg!(not(any(feature = "csr", feature = "hydrate", feature = "ssr"))) {
/// async fn fetch_cats(how_many: u32) -> Result<Vec<String>, ()> { Ok(vec![]) }
///
/// let (cat_count, set_cat_count) = create_signal::<u32>(cx, 1);
/// let (pending, set_pending) = create_signal(cx, false);
///
/// let cats = create_resource(cx, cat_count, |count| fetch_cats(count));
///
/// view! { cx,
/// <div>
/// <Transition
/// fallback={"Loading...".to_string()}
/// set_pending=set_pending
/// >
/// {move || {
/// cats.read().map(|data| match data {
/// Err(_) => view! { cx, <pre>"Error"</pre> },
/// Ok(cats) => view! { cx,
/// <div>{
/// cats.iter()
/// .map(|src| {
/// view! { cx,
/// <img src={src}/>
/// }
/// })
/// .collect::<Vec<_>>()
/// }</div>
/// },
/// })
/// }
/// }
/// </Transition>
/// </div>
/// };
/// # }
/// # });
/// ```
#[allow(non_snake_case)]
pub fn Transition<F, E, G>(cx: Scope, props: TransitionProps<F, E, G>) -> impl Fn() -> Child
where
F: IntoChild + Clone,
E: IntoChild,
G: Fn() -> E + 'static,
{
let context = SuspenseContext::new(cx);
// provide this SuspenseContext to any resources below it
provide_context(cx, context);
let child = (props.children)().swap_remove(0);
render_transition(cx, context, props.fallback, child, props.set_pending)
}
#[cfg(any(feature = "csr", feature = "hydrate"))]
fn render_transition<'a, F, E, G>(
cx: Scope,
context: SuspenseContext,
fallback: F,
child: G,
set_pending: Option<SignalSetter<bool>>,
) -> impl Fn() -> Child
where
F: IntoChild + Clone,
E: IntoChild,
G: Fn() -> E,
{
use std::cell::{Cell, RefCell};
let has_rendered_once = Cell::new(false);
let prev_child = RefCell::new(Child::Null);
move || {
if context.ready() {
has_rendered_once.set(true);
let current_child = (child)().into_child(cx);
*prev_child.borrow_mut() = current_child.clone();
if let Some(pending) = &set_pending {
pending.set(false);
}
current_child
} else if has_rendered_once.get() {
if let Some(pending) = &set_pending {
pending.set(true);
}
prev_child.borrow().clone()
} else {
if let Some(pending) = &set_pending {
pending.set(true);
}
let fallback = fallback.clone().into_child(cx);
*prev_child.borrow_mut() = fallback.clone();
fallback
}
}
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
fn render_transition<'a, F, E, G>(
cx: Scope,
context: SuspenseContext,
fallback: F,
orig_child: G,
set_pending: Option<SignalSetter<bool>>,
) -> impl Fn() -> Child
where
F: IntoChild + Clone,
E: IntoChild,
G: Fn() -> E + 'static,
{
use leptos_dom::IntoAttribute;
use leptos_macro::view;
_ = set_pending;
let initial = {
// run the child; we'll probably throw this away, but it will register resource reads
let mut child = orig_child().into_child(cx);
while let Child::Fn(f) = child {
child = (f.borrow_mut())();
}
// no resources were read under this, so just return the child
if context.pending_resources.get() == 0 {
child
}
// show the fallback, but also prepare to stream HTML
else {
let key = cx.current_fragment_key();
cx.register_suspense(context, &key, move || {
orig_child().into_child(cx).as_child_string()
});
// return the fallback for now, wrapped in fragment identifer
Child::Node(view! { cx, <div data-fragment-id=key>{fallback.into_child(cx)}</div> })
}
};
move || initial.clone()
}

View file

@ -137,11 +137,6 @@ features = [
"HtmlOptionElement"
]
[dev-dependencies]
leptos = { path = "../leptos", default-features = false, version = "0.0" }
leptos_macro = { path = "../leptos_macro", default-features = false, version = "0.0" }
[features]
web = ["leptos_reactive/csr"]
ssr = ["leptos_reactive/ssr"]