release 0.10.0

This commit is contained in:
Maccesch 2024-01-31 19:55:30 +00:00
parent 7c4d23a359
commit 6c14a8e6be
8 changed files with 82 additions and 73 deletions

View file

@ -3,7 +3,7 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] - ## [0.10.0] - 2024-010-31
### New Functions 🚀 ### New Functions 🚀

View file

@ -1,6 +1,6 @@
[package] [package]
name = "leptos-use" name = "leptos-use"
version = "0.9.0" version = "0.10.0"
edition = "2021" edition = "2021"
authors = ["Marc-Stefan Cassola"] authors = ["Marc-Stefan Cassola"]
categories = ["gui", "web-programming"] categories = ["gui", "web-programming"]

View file

@ -13,7 +13,7 @@
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a> <a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
<a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a> <a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a> <a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
<a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-63%20functions-%23EF3939" alt="63 Functions" /></a> <a href="https://leptos-use.rs"><img src="https://img.shields.io/badge/-69%20functions-%23EF3939" alt="69 Functions" /></a>
</p> </p>
<br/> <br/>
@ -92,5 +92,5 @@ This will create the function file in the src directory, scaffold an example dir
| <= 0.3 | 0.3 | | <= 0.3 | 0.3 |
| 0.4, 0.5, 0.6 | 0.4 | | 0.4, 0.5, 0.6 | 0.4 |
| 0.7, 0.8, 0.9 | 0.5 | | 0.7, 0.8, 0.9 | 0.5 |
| main | 0.6.0-beta | | 0.10 | 0.6 |

View file

@ -12,6 +12,6 @@
<a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a> <a href="https://crates.io/crates/leptos-use"><img src="https://img.shields.io/crates/v/leptos-use.svg?label=&color=%232C1275" alt="Crates.io"/></a>
<a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a> <a href="https://leptos-use.rs/server_side_rendering.html"><img src="https://img.shields.io/badge/-SSR-%236a214b" alt="SSR"></a>
<a href="./get_started.html"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a> <a href="./get_started.html"><img src="https://img.shields.io/badge/-docs%20%26%20demos-%239A233F" alt="Docs & Demos"></a>
<a href="./functions.html"><img src="https://img.shields.io/badge/-63%20functions-%23EF3939" alt="63 Functions" /></a> <a href="./functions.html"><img src="https://img.shields.io/badge/-69%20functions-%23EF3939" alt="69 Functions" /></a>
</p> </p>
</div> </div>

View file

@ -1,3 +0,0 @@
# use_webtransport
<!-- cmdrun python3 ../extract_doc_comment.py use_webtransport -->

View file

@ -16,11 +16,6 @@ pub fn App() -> impl IntoView {
// Provides context that manages stylesheets, titles, meta tags, etc. // Provides context that manages stylesheets, titles, meta tags, etc.
provide_meta_context(); provide_meta_context();
#[cfg(feature = "ssr")]
{
expect_context::<http::request::Parts>();
}
view! { view! {
<Stylesheet id="leptos" href="/pkg/start-axum.css"/> <Stylesheet id="leptos" href="/pkg/start-axum.css"/>
@ -76,11 +71,11 @@ fn HomePage() -> impl IntoView {
let is_dark_preferred = use_preferred_dark(); let is_dark_preferred = use_preferred_dark();
let (session_cookie, _) = use_cookie_with_options::<String, FromToStringCodec>( let (test_cookie, _) = use_cookie_with_options::<String, FromToStringCodec>(
"session", "test-cookie",
UseCookieOptions::<String, _>::default() UseCookieOptions::<String, _>::default()
.max_age(3600) .max_age(3000)
.default_value(Some("Bogus session string".to_owned())), .default_value(Some("Bogus string".to_owned())),
); );
view! { view! {
@ -96,7 +91,7 @@ fn HomePage() -> impl IntoView {
<p>{timestamp}</p> <p>{timestamp}</p>
<p>Dark preferred: {is_dark_preferred}</p> <p>Dark preferred: {is_dark_preferred}</p>
<LocalStorageTest/> <LocalStorageTest/>
<p>Session cookie: {session_cookie}</p> <p>Test cookie: {move || test_cookie().unwrap_or("<Expired>".to_string())}</p>
} }
} }

View file

@ -10,6 +10,13 @@ use std::rc::Rc;
/// SSR-friendly and reactive cookie access. /// SSR-friendly and reactive cookie access.
/// ///
/// You can use this function multiple times in your for the same cookie and they're signals will synchronize
/// (even across windows/tabs). But there is no way to listen to changes to `document.cookie` directly so in case
/// something outside of this function changes the cookie, the signal will **not** be updated.
///
/// When the options `max_age` or `expire` is given then the returned signal will
/// automatically turn to `None` after that time.
///
/// ## Demo /// ## Demo
/// ///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_cookie) /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_cookie)
@ -109,7 +116,7 @@ use std::rc::Rc;
/// .ssr_cookies_header_getter(|| { /// .ssr_cookies_header_getter(|| {
/// #[cfg(feature = "ssr")] /// #[cfg(feature = "ssr")]
/// { /// {
/// "Somehow get the value of the cookie header as a string".to_owned() /// Some("Somehow get the value of the cookie header as a string".to_owned())
/// } /// }
/// }) /// })
/// .ssr_set_cookie(|cookie: &Cookie| { /// .ssr_set_cookie(|cookie: &Cookie| {
@ -159,7 +166,7 @@ where
} = options; } = options;
let delay = if let Some(max_age) = max_age { let delay = if let Some(max_age) = max_age {
Some(max_age * 1000) Some(max_age)
} else { } else {
expires.map(|expires| expires * 1000 - now() as i64) expires.map(|expires| expires * 1000 - now() as i64)
}; };
@ -179,18 +186,20 @@ where
let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter); let ssr_cookies_header_getter = Rc::clone(&ssr_cookies_header_getter);
jar.update_value(|jar| { jar.update_value(|jar| {
*jar = load_and_parse_cookie_jar(ssr_cookies_header_getter); if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) {
*jar = new_jar;
set_cookie.set( set_cookie.set(
jar.get(cookie_name) jar.get(cookie_name)
.and_then(|c| { .and_then(|c| {
codec codec
.decode(c.value().to_string()) .decode(c.value().to_string())
.map_err(|err| on_error(err)) .map_err(|err| on_error(err))
.ok() .ok()
}) })
.or(default_value), .or(default_value),
); );
}
}); });
handle_expiration(delay, set_cookie); handle_expiration(delay, set_cookie);
@ -433,7 +442,7 @@ pub struct UseCookieOptions<T, Err> {
/// Getter function to return the string value of the cookie header. /// Getter function to return the string value of the cookie header.
/// When you use one of the features "axum" or "actix" there's a valid default implementation provided. /// When you use one of the features "axum" or "actix" there's a valid default implementation provided.
ssr_cookies_header_getter: Rc<dyn Fn() -> String>, ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>,
/// Function to add a set cookie header to the response on the server. /// Function to add a set cookie header to the response on the server.
/// When you use one of the features "axum" or "actix" there's a valid default implementation provided. /// When you use one of the features "axum" or "actix" there's a valid default implementation provided.
@ -476,30 +485,33 @@ impl<T, Err> Default for UseCookieOptions<T, Err> {
let headers; let headers;
#[cfg(feature = "actix")] #[cfg(feature = "actix")]
{ {
headers = expect_context::<actix_web::HttpRequest>().headers().clone(); headers = use_context::<actix_web::HttpRequest>()
.map(|req| req.headers().clone());
} }
#[cfg(feature = "axum")] #[cfg(feature = "axum")]
{ {
headers = expect_context::<http1::request::Parts>().headers; headers = use_context::<http1::request::Parts>().map(|parts| parts.headers);
} }
#[cfg(all(not(feature = "axum"), not(feature = "actix")))] #[cfg(all(not(feature = "axum"), not(feature = "actix")))]
{ {
leptos::logging::warn!("If you're using use_cookie without the feature `axum` or `actix` enabled, you should provide the option `ssr_cookies_header_getter`"); leptos::logging::warn!("If you're using use_cookie without the feature `axum` or `actix` enabled, you should provide the option `ssr_cookies_header_getter`");
"".to_owned() None
} }
#[cfg(any(feature = "axum", feature = "actix"))] #[cfg(any(feature = "axum", feature = "actix"))]
headers headers.map(|headers| {
.get(COOKIE) headers
.cloned() .get(COOKIE)
.unwrap_or_else(|| HeaderValue::from_static("")) .cloned()
.to_str() .unwrap_or_else(|| HeaderValue::from_static(""))
.unwrap_or_default() .to_str()
.to_owned() .unwrap_or_default()
.to_owned()
})
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
"".to_owned() None
}), }),
ssr_set_cookie: Rc::new(|cookie: &Cookie| { ssr_set_cookie: Rc::new(|cookie: &Cookie| {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -527,12 +539,12 @@ impl<T, Err> Default for UseCookieOptions<T, Err> {
#[cfg(any(feature = "axum", feature = "actix"))] #[cfg(any(feature = "axum", feature = "actix"))]
{ {
let response_options = expect_context::<ResponseOptions>(); if let Some(response_options) = use_context::<ResponseOptions>() {
if let Ok(header_value) =
if let Ok(header_value) = HeaderValue::from_str(&cookie.encoded().to_string())
HeaderValue::from_str(&cookie.encoded().to_string()) {
{ response_options.insert_header(SET_COOKIE, header_value);
response_options.insert_header(SET_COOKIE, header_value); }
} }
} }
} }
@ -546,7 +558,9 @@ impl<T, Err> Default for UseCookieOptions<T, Err> {
} }
} }
fn read_cookies_string(ssr_cookies_header_getter: Rc<dyn Fn() -> String>) -> String { fn read_cookies_string(
ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>,
) -> Option<String> {
let cookies; let cookies;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
@ -562,7 +576,7 @@ fn read_cookies_string(ssr_cookies_header_getter: Rc<dyn Fn() -> String>) -> Str
let js_value: wasm_bindgen::JsValue = leptos::document().into(); let js_value: wasm_bindgen::JsValue = leptos::document().into();
let document: web_sys::HtmlDocument = js_value.unchecked_into(); let document: web_sys::HtmlDocument = js_value.unchecked_into();
cookies = document.cookie().unwrap_or_default(); cookies = Some(document.cookie().unwrap_or_default());
} }
cookies cookies
@ -657,7 +671,7 @@ fn write_client_cookie(
same_site: Option<SameSite>, same_site: Option<SameSite>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
ssr_cookies_header_getter: Rc<dyn Fn() -> String>, ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>,
) { ) {
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@ -693,18 +707,19 @@ fn update_client_cookie_jar(
same_site: Option<SameSite>, same_site: Option<SameSite>,
secure: bool, secure: bool,
http_only: bool, http_only: bool,
ssr_cookies_header_getter: Rc<dyn Fn() -> String>, ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>,
) { ) {
*jar = load_and_parse_cookie_jar(ssr_cookies_header_getter); if let Some(new_jar) = load_and_parse_cookie_jar(ssr_cookies_header_getter) {
*jar = new_jar;
if let Some(value) = value {
let cookie = build_cookie_from_options(
name, max_age, expires, http_only, secure, path, same_site, domain, value,
);
if let Some(value) = value { jar.add_original(cookie);
let cookie = build_cookie_from_options( } else {
name, max_age, expires, http_only, secure, path, same_site, domain, value, jar.force_remove(name);
); }
jar.add_original(cookie);
} else {
jar.force_remove(name);
} }
} }
@ -791,15 +806,17 @@ fn write_server_cookie(
} }
} }
fn load_and_parse_cookie_jar(ssr_cookies_header_getter: Rc<dyn Fn() -> String>) -> CookieJar { fn load_and_parse_cookie_jar(
let mut jar = CookieJar::new(); ssr_cookies_header_getter: Rc<dyn Fn() -> Option<String>>,
let cookies = read_cookies_string(ssr_cookies_header_getter); ) -> Option<CookieJar> {
read_cookies_string(ssr_cookies_header_getter).map(|cookies| {
let mut jar = CookieJar::new();
for cookie in Cookie::split_parse_encoded(cookies).flatten() {
jar.add_original(cookie);
}
for cookie in Cookie::split_parse_encoded(cookies).flatten() { jar
jar.add_original(cookie); })
}
jar
} }
#[derive(Default, Copy, Clone)] #[derive(Default, Copy, Clone)]

View file

@ -41,11 +41,11 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn {
let beta = || None; let beta = || None;
let gamma = || None; let gamma = || None;
} else { } else {
use crate::{use_event_listener_with_options, UseEventListenerOptions}; use crate::{use_event_listener_with_options, UseEventListenerOptions, use_supported};
use leptos::ev::deviceorientation; use leptos::ev::deviceorientation;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
let is_supported = Signal::derive(|| js_sys::Reflect::has( let is_supported = use_supported(|| js_sys::Reflect::has(
&window(), &window(),
&JsValue::from_str("DeviceOrientationEvent"), &JsValue::from_str("DeviceOrientationEvent"),
).unwrap_or(false)); ).unwrap_or(false));
@ -54,7 +54,7 @@ pub fn use_device_orientation() -> UseDeviceOrientationReturn {
let (beta, set_beta) = create_signal(None); let (beta, set_beta) = create_signal(None);
let (gamma, set_gamma) = create_signal(None); let (gamma, set_gamma) = create_signal(None);
if is_supported.get() { if is_supported.get_untracked() {
let cleanup = use_event_listener_with_options( let cleanup = use_event_listener_with_options(
window(), window(),
deviceorientation, deviceorientation,