use crate::{ServerFn, ServerFnError};
use leptos_reactive::{
create_rw_signal, signal_prelude::*, spawn_local, store_value, ReadSignal,
RwSignal, Scope, StoredValue,
};
use std::{future::Future, pin::Pin, rc::Rc};
pub struct Action<I, O>(StoredValue<ActionState<I, O>>)
where
I: 'static,
O: 'static;
impl<I, O> Action<I, O>
where
I: 'static,
O: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
self.0.with_value(|a| a.dispatch(input))
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn pending(&self) -> ReadSignal<bool> {
self.0.with_value(|a| a.pending.read_only())
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn set_pending(&self, pending: bool) {
self.0.try_with_value(|a| a.pending.set(pending));
}
pub fn url(&self) -> Option<String> {
self.0.with_value(|a| a.url.as_ref().cloned())
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update_value(|state| {
state.url = if prefix.is_empty() {
Some(T::url().to_string())
} else {
Some(prefix.to_string() + "/" + T::url())
};
});
self
}
pub fn version(&self) -> RwSignal<usize> {
self.0.with_value(|a| a.version)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn input(&self) -> RwSignal<Option<I>> {
self.0.with_value(|a| a.input)
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn value(&self) -> RwSignal<Option<O>> {
self.0.with_value(|a| a.value)
}
}
impl<I, O> Clone for Action<I, O>
where
I: 'static,
O: 'static,
{
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<I, O> Copy for Action<I, O>
where
I: 'static,
O: 'static,
{
}
struct ActionState<I, O>
where
I: 'static,
O: 'static,
{
pub version: RwSignal<usize>,
pub input: RwSignal<Option<I>>,
pub value: RwSignal<Option<O>>,
pending: RwSignal<bool>,
url: Option<String>,
#[allow(clippy::complexity)]
action_fn: Rc<dyn Fn(&I) -> Pin<Box<dyn Future<Output = O>>>>,
}
impl<I, O> ActionState<I, O>
where
I: 'static,
O: 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn dispatch(&self, input: I) {
let fut = (self.action_fn)(&input);
self.input.set(Some(input));
let input = self.input;
let version = self.version;
let pending = self.pending;
let value = self.value;
pending.set(true);
spawn_local(async move {
let new_value = fut.await;
value.set(Some(new_value));
input.set(None);
pending.set(false);
version.update(|n| *n += 1);
})
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_action<I, O, F, Fu>(cx: Scope, action_fn: F) -> Action<I, O>
where
I: 'static,
O: 'static,
F: Fn(&I) -> Fu + 'static,
Fu: Future<Output = O> + 'static,
{
let version = create_rw_signal(cx, 0);
let input = create_rw_signal(cx, None);
let value = create_rw_signal(cx, None);
let pending = create_rw_signal(cx, false);
let action_fn = Rc::new(move |input: &I| {
let fut = action_fn(input);
Box::pin(fut) as Pin<Box<dyn Future<Output = O>>>
});
Action(store_value(
cx,
ActionState {
version,
url: None,
input,
value,
pending,
action_fn,
},
))
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
pub fn create_server_action<S>(
cx: Scope,
) -> Action<S, Result<S::Output, ServerFnError>>
where
S: Clone + ServerFn,
{
#[cfg(feature = "ssr")]
let c = move |args: &S| S::call_fn(args.clone(), cx);
#[cfg(not(feature = "ssr"))]
let c = move |args: &S| S::call_fn_client(args.clone(), cx);
create_action(cx, c).using_server_fn::<S>()
}