Merge branch 'main' into leptos-0.7

# Conflicts:
#	Cargo.toml
#	examples/use_broadcast_channel/Cargo.toml
#	examples/use_cookie/Cargo.toml
#	examples/use_websocket/Cargo.toml
#	src/use_websocket.rs
This commit is contained in:
Maccesch 2024-08-24 21:44:34 +01:00
commit b6a5d36df1
13 changed files with 105 additions and 57 deletions

View file

@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `use_prefers_reduced_motion` - `use_prefers_reduced_motion`
### Breaking Change 🛠
- `use_websocket` now supports different types for sending and receiving messages
### Change 🔥
- There is now a feature for almost every function to get better compile and rust-analyzer times.
## [0.12.0] - 2024-08-14 ## [0.12.0] - 2024-08-14
> Make sure you also update `cargo-leptos` to the latest version if you use that. > Make sure you also update `cargo-leptos` to the latest version if you use that.

View file

@ -16,7 +16,7 @@ homepage = "https://leptos-use.rs"
actix-web = { version = "4", optional = true, default-features = false } actix-web = { version = "4", optional = true, default-features = false }
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }
cfg-if = "1" cfg-if = "1"
codee = { version = "0.1", optional = true } codee = { version = "0.2", optional = true }
cookie = { version = "0.18", features = ["percent-encode"], optional = true } cookie = { version = "0.18", features = ["percent-encode"], optional = true }
default-struct-builder = "0.5" default-struct-builder = "0.5"
futures-util = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true }
@ -40,7 +40,7 @@ wasm-bindgen-futures = "0.4"
web-sys = { version = "=0.3.70", optional = true } web-sys = { version = "=0.3.70", optional = true }
[dev-dependencies] [dev-dependencies]
codee = { version = "0.1", features = [ codee = { version = "0.2", features = [
"json_serde", "json_serde",
"msgpack_serde", "msgpack_serde",
"base64", "base64",

View file

@ -94,5 +94,8 @@ panic = "abort"
[lib] [lib]
[workspace.dependencies]
codee = { version = "0.2" }
[package.metadata.leptos] [package.metadata.leptos]
lib-profile-release = "wasm-release" lib-profile-release = "wasm-release"

View file

@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
axum = { version = "0.7", optional = true } axum = { version = "0.7", optional = true }
codee = "0.1" codee.workspace = true
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
cfg-if = "1" cfg-if = "1"

View file

@ -8,7 +8,7 @@ leptos = { git = "https://github.com/leptos-rs/leptos", features = [
"nightly", "nightly",
"csr", "csr",
] } ] }
codee = "0.1" codee.workspace = true
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -8,7 +8,7 @@ leptos = { git = "https://github.com/leptos-rs/leptos", features = [
"nightly", "nightly",
"csr", "csr",
] } ] }
codee = "0.1" codee.workspace = true
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
codee = { version = "0.1", features = ["json_serde"] } codee = { workspace = true, features = ["json_serde"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
leptos = { git = "https://github.com/leptos-rs/leptos", features = [ leptos = { git = "https://github.com/leptos-rs/leptos", features = [

View file

@ -8,7 +8,7 @@ leptos = { git = "https://github.com/leptos-rs/leptos", features = [
"nightly", "nightly",
"csr", "csr",
] } ] }
codee = { version = "0.1", features = ["msgpack_serde"] } codee = { workspace = true, features = ["msgpack_serde"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"
console_log = "1" console_log = "1"
log = "0.4" log = "0.4"

View file

@ -33,7 +33,7 @@ fn Demo() -> impl IntoView {
open, open,
close, close,
.. ..
} = use_websocket::<Apple, MsgpackSerdeCodec>("wss://echo.websocket.events/"); } = use_websocket::<Apple, Apple, MsgpackSerdeCodec>("wss://echo.websocket.events/");
let send_message = move |_| { let send_message = move |_| {
let m = Apple { let m = Apple {
@ -101,7 +101,7 @@ fn Demo() -> impl IntoView {
close: close2, close: close2,
message: message2, message: message2,
.. ..
} = use_websocket_with_options::<String, FromToStringCodec>( } = use_websocket_with_options::<String, String, FromToStringCodec>(
"wss://echo.websocket.events/", "wss://echo.websocket.events/",
UseWebSocketOptions::default() UseWebSocketOptions::default()
.immediate(false) .immediate(false)

View file

@ -3,6 +3,7 @@ use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::{wrappers::read::Signal, *}; use leptos::prelude::{wrappers::read::Signal, *};
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::JsValue;
/// Reactive [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notification). /// Reactive [Notification API](https://developer.mozilla.org/en-US/docs/Web/API/Notification).
/// ///
@ -219,7 +220,6 @@ impl From<NotificationDirection> for web_sys::NotificationDirection {
/// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info. /// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info.
/// ///
/// The following implementations are missing: /// The following implementations are missing:
/// - `vibrate`
#[derive(DefaultBuilder, Clone)] #[derive(DefaultBuilder, Clone)]
#[cfg_attr(feature = "ssr", allow(dead_code))] #[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct UseWebNotificationOptions { pub struct UseWebNotificationOptions {
@ -268,10 +268,15 @@ pub struct UseWebNotificationOptions {
renotify: bool, renotify: bool,
/// A boolean value specifying whether the notification should be silent, regardless of the device settings. /// A boolean value specifying whether the notification should be silent, regardless of the device settings.
/// The default is `false`, which means the notification is not silent. If `true`, then the notification will be silent. /// The default is `null`, which means the notification is not silent. If `true`, then the notification will be silent.
#[builder(into)] #[builder(into)]
silent: Option<bool>, silent: Option<bool>,
/// A `Vec<u16>` specifying the vibration pattern in milliseconds for vibrating and not vibrating.
/// The last entry can be a vibration since it stops automatically after each period.
#[builder(into)]
vibrate: Option<Vec<u16>>,
/// Called when the user clicks on displayed `Notification`. /// Called when the user clicks on displayed `Notification`.
on_click: Rc<dyn Fn(web_sys::Event)>, on_click: Rc<dyn Fn(web_sys::Event)>,
@ -299,6 +304,7 @@ impl Default for UseWebNotificationOptions {
require_interaction: false, require_interaction: false,
renotify: false, renotify: false,
silent: None, silent: None,
vibrate: None,
on_click: Rc::new(|_| {}), on_click: Rc::new(|_| {}),
on_close: Rc::new(|_| {}), on_close: Rc::new(|_| {}),
on_error: Rc::new(|_| {}), on_error: Rc::new(|_| {}),
@ -336,6 +342,9 @@ impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions {
web_sys_options.set_tag(tag); web_sys_options.set_tag(tag);
} }
if let Some(vibrate) = &options.vibrate {
web_sys_options.set_vibrate(&vibration_pattern_to_jsvalue(vibrate));
}
web_sys_options web_sys_options
} }
} }
@ -345,7 +354,6 @@ impl From<&UseWebNotificationOptions> for web_sys::NotificationOptions {
/// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info. /// See [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/notification) for more info.
/// ///
/// The following implementations are missing: /// The following implementations are missing:
/// - `vibrate`
#[derive(DefaultBuilder, Default)] #[derive(DefaultBuilder, Default)]
#[cfg_attr(feature = "ssr", allow(dead_code))] #[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct ShowOptions { pub struct ShowOptions {
@ -396,9 +404,14 @@ pub struct ShowOptions {
renotify: Option<bool>, renotify: Option<bool>,
/// A boolean value specifying whether the notification should be silent, regardless of the device settings. /// A boolean value specifying whether the notification should be silent, regardless of the device settings.
/// The default is `false`, which means the notification is not silent. If `true`, then the notification will be silent. /// The default is `null`, which means the notification is not silent. If `true`, then the notification will be silent.
#[builder(into)] #[builder(into)]
silent: Option<bool>, silent: Option<bool>,
/// A `Vec<u16>` specifying the vibration pattern in milliseconds for vibrating and not vibrating.
/// The last entry can be a vibration since it stops automatically after each period.
#[builder(into)]
vibrate: Option<Vec<u16>>,
} }
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
@ -439,6 +452,10 @@ impl ShowOptions {
if let Some(silent) = self.silent { if let Some(silent) = self.silent {
options.set_silent(Some(silent)); options.set_silent(Some(silent));
} }
if let Some(vibrate) = &self.vibrate {
options.set_vibrate(&vibration_pattern_to_jsvalue(vibrate));
}
} }
} }
@ -453,6 +470,15 @@ fn browser_supports_notifications() -> bool {
false false
} }
/// Helper function to convert a slice of `u16` into a `JsValue` array that represents a vibration pattern
fn vibration_pattern_to_jsvalue(pattern: &[u16]) -> JsValue {
let array = js_sys::Array::new();
for &value in pattern.iter() {
array.push(&JsValue::from(value));
}
array.into()
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
/// The permission to send notifications /// The permission to send notifications
pub enum NotificationPermission { pub enum NotificationPermission {

View file

@ -1,17 +1,15 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
use crate::{core::ConnectionReadyState, ReconnectLimit};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use codee::{CodecError, Decoder, Encoder, HybridCoderError, HybridDecoder, HybridEncoder};
use default_struct_builder::DefaultBuilder;
use js_sys::Array;
use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*}; use leptos::{leptos_dom::helpers::TimeoutHandle, prelude::*};
use std::marker::PhantomData;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use crate::{core::ConnectionReadyState, ReconnectLimit};
use codee::{
CodecError, Decoder, Encoder, HybridCoderError, HybridDecoder, HybridEncoder, IsBinary,
};
use default_struct_builder::DefaultBuilder;
use js_sys::Array;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket}; use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
@ -44,7 +42,7 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// open, /// open,
/// close, /// close,
/// .. /// ..
/// } = use_websocket::<String, FromToStringCodec>("wss://echo.websocket.events/"); /// } = use_websocket::<String, String, FromToStringCodec>("wss://echo.websocket.events/");
/// ///
/// let send_message = move |_| { /// let send_message = move |_| {
/// send(&"Hello, world!".to_string()); /// send(&"Hello, world!".to_string());
@ -97,7 +95,7 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// message, /// message,
/// send, /// send,
/// .. /// ..
/// } = use_websocket::<SomeData, MsgpackSerdeCodec>("wss://some.websocket.server/"); /// } = use_websocket::<SomeData, SomeData, MsgpackSerdeCodec>("wss://some.websocket.server/");
/// ///
/// let send_data = move || { /// let send_data = move || {
/// send(&SomeData { /// send(&SomeData {
@ -188,7 +186,7 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// message, /// message,
/// send, /// send,
/// .. /// ..
/// } = use_websocket::<String, FromToStringCodec>("ws:://some.websocket.io"); /// } = use_websocket::<String, String, FromToStringCodec>("ws:://some.websocket.io");
/// ///
/// provide_context(WebsocketContext::new(message, Arc::new(send.clone()))); /// provide_context(WebsocketContext::new(message, Arc::new(send.clone())));
/// # /// #
@ -228,45 +226,47 @@ use web_sys::{BinaryType, CloseEvent, Event, MessageEvent, WebSocket};
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server the returned functions amount to no-ops. /// On the server the returned functions amount to no-ops.
pub fn use_websocket<T, C>( pub fn use_websocket<Tx, Rx, C>(
url: &str, url: &str,
) -> UseWebSocketReturn< ) -> UseWebSocketReturn<
T, Tx,
Rx,
impl Fn() + Clone + 'static, impl Fn() + Clone + 'static,
impl Fn() + Clone + 'static, impl Fn() + Clone + 'static,
impl Fn(&T) + Clone + 'static, impl Fn(&Tx) + Clone + 'static,
> >
where where
T: Send + Sync + 'static, Tx: Send + Sync + 'static,
C: Encoder<T> + Decoder<T>, Rx: Send + Sync + 'static,
C: IsBinary<T, <C as Decoder<T>>::Encoded>, C: Encoder<Tx> + Decoder<Rx>,
C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>, C: HybridEncoder<Tx, <C as Encoder<Tx>>::Encoded, Error = <C as Encoder<Tx>>::Error>,
C: HybridEncoder<T, <C as Encoder<T>>::Encoded, Error = <C as Encoder<T>>::Error>, C: HybridDecoder<Rx, <C as Decoder<Rx>>::Encoded, Error = <C as Decoder<Rx>>::Error>,
{ {
use_websocket_with_options::<T, C>(url, UseWebSocketOptions::default()) use_websocket_with_options::<Tx, Rx, C>(url, UseWebSocketOptions::default())
} }
/// Version of [`use_websocket`] that takes `UseWebSocketOptions`. See [`use_websocket`] for how to use. /// Version of [`use_websocket`] that takes `UseWebSocketOptions`. See [`use_websocket`] for how to use.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn use_websocket_with_options<T, C>( pub fn use_websocket_with_options<Tx, Rx, C>(
url: &str, url: &str,
options: UseWebSocketOptions< options: UseWebSocketOptions<
T, Rx,
HybridCoderError<<C as Encoder<T>>::Error>, HybridCoderError<<C as Encoder<Tx>>::Error>,
HybridCoderError<<C as Decoder<T>>::Error>, HybridCoderError<<C as Decoder<Rx>>::Error>,
>, >,
) -> UseWebSocketReturn< ) -> UseWebSocketReturn<
T, Tx,
Rx,
impl Fn() + Clone + 'static, impl Fn() + Clone + 'static,
impl Fn() + Clone + 'static, impl Fn() + Clone + 'static,
impl Fn(&T) + Clone + 'static, impl Fn(&Tx) + Clone + 'static,
> >
where where
T: Send + Sync + 'static, Tx: Send + Sync + 'static,
C: Encoder<T> + Decoder<T>, Rx: Send + Sync + 'static,
C: IsBinary<T, <C as Decoder<T>>::Encoded>, C: Encoder<Tx> + Decoder<Rx>,
C: HybridDecoder<T, <C as Decoder<T>>::Encoded, Error = <C as Decoder<T>>::Error>, C: HybridEncoder<Tx, <C as Encoder<Tx>>::Encoded, Error = <C as Encoder<Tx>>::Error>,
C: HybridEncoder<T, <C as Encoder<T>>::Encoded, Error = <C as Encoder<T>>::Error>, C: HybridDecoder<Rx, <C as Decoder<Rx>>::Encoded, Error = <C as Decoder<Rx>>::Error>,
{ {
let url = normalize_url(url); let url = normalize_url(url);
@ -301,7 +301,11 @@ where
let reconnect_ref: StoredValue<Option<Arc<dyn Fn() + Send + Sync>>> = let reconnect_ref: StoredValue<Option<Arc<dyn Fn() + Send + Sync>>> =
StoredValue::new(None); StoredValue::new(None);
reconnect_ref.set_value({ reconnect_ref.set_value({
let unmounted = Arc::clone(&unmounted);
Some(Arc::new(move || { Some(Arc::new(move || {
let unmounted = Arc::clone(&unmounted);
if !manually_closed_ref.get_value() if !manually_closed_ref.get_value()
&& !reconnect_limit.is_exceeded_by(reconnect_times_ref.get_value()) && !reconnect_limit.is_exceeded_by(reconnect_times_ref.get_value())
&& ws_ref && ws_ref
@ -311,6 +315,9 @@ where
reconnect_timer_ref.set_value( reconnect_timer_ref.set_value(
set_timeout_with_handle( set_timeout_with_handle(
move || { move || {
if unmounted.get() {
return;
}
if let Some(connect) = connect_ref.get_value() { if let Some(connect) = connect_ref.get_value() {
connect(); connect();
reconnect_times_ref.update_value(|current| *current += 1); reconnect_times_ref.update_value(|current| *current += 1);
@ -552,8 +559,8 @@ where
let send = { let send = {
let on_error = Arc::clone(&on_error); let on_error = Arc::clone(&on_error);
move |value: &T| { move |value: &Tx| {
if C::is_binary() { if C::is_binary_encoder() {
match C::encode_bin(value) { match C::encode_bin(value) {
Ok(val) => send_bytes(&val), Ok(val) => send_bytes(&val),
Err(err) => on_error(CodecError::Encode(err).into()), Err(err) => on_error(CodecError::Encode(err).into()),
@ -607,6 +614,7 @@ where
open, open,
close, close,
send, send,
_marker: PhantomData,
} }
} }
@ -614,15 +622,15 @@ type ArcFnBytes = Arc<dyn Fn(&[u8]) + Send + Sync>;
/// Options for [`use_websocket_with_options`]. /// Options for [`use_websocket_with_options`].
#[derive(DefaultBuilder)] #[derive(DefaultBuilder)]
pub struct UseWebSocketOptions<T, E, D> pub struct UseWebSocketOptions<Rx, E, D>
where where
T: ?Sized, Rx: ?Sized,
{ {
/// `WebSocket` connect callback. /// `WebSocket` connect callback.
on_open: Arc<dyn Fn(Event) + Send + Sync>, on_open: Arc<dyn Fn(Event) + Send + Sync>,
/// `WebSocket` message callback for typed message decoded by codec. /// `WebSocket` message callback for typed message decoded by codec.
#[builder(skip)] #[builder(skip)]
on_message: Arc<dyn Fn(&T) + Send + Sync>, on_message: Arc<dyn Fn(&Rx) + Send + Sync>,
/// `WebSocket` message callback for text. /// `WebSocket` message callback for text.
on_message_raw: Arc<dyn Fn(&str) + Send + Sync>, on_message_raw: Arc<dyn Fn(&str) + Send + Sync>,
/// `WebSocket` message callback for binary. /// `WebSocket` message callback for binary.
@ -645,7 +653,7 @@ where
protocols: Option<Vec<String>>, protocols: Option<Vec<String>>,
} }
impl<T: ?Sized, E, D> UseWebSocketOptions<T, E, D> { impl<Rx: ?Sized, E, D> UseWebSocketOptions<Rx, E, D> {
/// `WebSocket` error callback. /// `WebSocket` error callback.
pub fn on_error<F>(self, handler: F) -> Self pub fn on_error<F>(self, handler: F) -> Self
where where
@ -660,7 +668,7 @@ impl<T: ?Sized, E, D> UseWebSocketOptions<T, E, D> {
/// `WebSocket` message callback for typed message decoded by codec. /// `WebSocket` message callback for typed message decoded by codec.
pub fn on_message<F>(self, handler: F) -> Self pub fn on_message<F>(self, handler: F) -> Self
where where
F: Fn(&T) + Send + Sync + 'static, F: Fn(&Rx) + Send + Sync + 'static,
{ {
Self { Self {
on_message: Arc::new(handler), on_message: Arc::new(handler),
@ -669,7 +677,7 @@ impl<T: ?Sized, E, D> UseWebSocketOptions<T, E, D> {
} }
} }
impl<T: ?Sized, E, D> Default for UseWebSocketOptions<T, E, D> { impl<Rx: ?Sized, E, D> Default for UseWebSocketOptions<Rx, E, D> {
fn default() -> Self { fn default() -> Self {
Self { Self {
on_open: Arc::new(|_| {}), on_open: Arc::new(|_| {}),
@ -688,17 +696,18 @@ impl<T: ?Sized, E, D> Default for UseWebSocketOptions<T, E, D> {
/// Return type of [`use_websocket`]. /// Return type of [`use_websocket`].
#[derive(Clone)] #[derive(Clone)]
pub struct UseWebSocketReturn<T, OpenFn, CloseFn, SendFn> pub struct UseWebSocketReturn<Tx, Rx, OpenFn, CloseFn, SendFn>
where where
T: Send + Sync + 'static, Tx: Send + Sync + 'static,
Rx: Send + Sync + 'static,
OpenFn: Fn() + Clone + 'static, OpenFn: Fn() + Clone + 'static,
CloseFn: Fn() + Clone + 'static, CloseFn: Fn() + Clone + 'static,
SendFn: Fn(&T) + Clone + 'static, SendFn: Fn(&Tx) + Clone + 'static,
{ {
/// The current state of the `WebSocket` connection. /// The current state of the `WebSocket` connection.
pub ready_state: Signal<ConnectionReadyState>, pub ready_state: Signal<ConnectionReadyState>,
/// Latest message received from `WebSocket`. /// Latest message received from `WebSocket`.
pub message: Signal<Option<T>>, pub message: Signal<Option<Rx>>,
/// The `WebSocket` instance. /// The `WebSocket` instance.
pub ws: Option<WebSocket>, pub ws: Option<WebSocket>,
/// Opens the `WebSocket` connection /// Opens the `WebSocket` connection
@ -707,6 +716,8 @@ where
pub close: CloseFn, pub close: CloseFn,
/// Sends data through the socket /// Sends data through the socket
pub send: SendFn, pub send: SendFn,
_marker: PhantomData<Tx>,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

View file

@ -1,4 +1,4 @@
use crate::{watch_with_options, utils::DebounceOptions, WatchOptions}; use crate::{utils::DebounceOptions, watch_with_options, WatchOptions};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::prelude::*; use leptos::prelude::*;

View file

@ -1,4 +1,4 @@
use crate::{watch_with_options, utils::ThrottleOptions, WatchOptions}; use crate::{utils::ThrottleOptions, watch_with_options, WatchOptions};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
/// A throttled version of `leptos::watch`. /// A throttled version of `leptos::watch`.