use crate::{ServerFn, ServerFnError};
use leptos_reactive::{
create_rw_signal, spawn_local, store_value, ReadSignal, RwSignal, Scope, StoredValue,
};
use std::{future::Future, pin::Pin, rc::Rc};
pub struct MultiAction<I, O>(StoredValue<MultiActionState<I, O>>)
where
I: 'static,
O: 'static;
impl<I, O> MultiAction<I, O>
where
I: 'static,
O: 'static,
{
}
impl<I, O> Clone for MultiAction<I, O>
where
I: 'static,
O: 'static,
{
fn clone(&self) -> Self {
Self(self.0)
}
}
impl<I, O> Copy for MultiAction<I, O>
where
I: 'static,
O: 'static,
{
}
impl<I, O> MultiAction<I, O>
where
I: 'static,
O: 'static,
{
pub fn dispatch(&self, input: I) {
self.0.with(|a| a.dispatch(input))
}
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
self.0.with(|a| a.submissions())
}
pub fn url(&self) -> Option<String> {
self.0.with(|a| a.url.as_ref().cloned())
}
pub fn version(&self) -> RwSignal<usize> {
self.0.with(|a| a.version)
}
pub fn using_server_fn<T: ServerFn>(self) -> Self {
let prefix = T::prefix();
self.0.update(|a| {
a.url = if prefix.is_empty() {
Some(T::url().to_string())
} else {
Some(prefix.to_string() + "/" + T::url())
};
});
self
}
}
struct MultiActionState<I, O>
where
I: 'static,
O: 'static,
{
cx: Scope,
pub version: RwSignal<usize>,
submissions: RwSignal<Vec<Submission<I, O>>>,
url: Option<String>,
#[allow(clippy::complexity)]
action_fn: Rc<dyn Fn(&I) -> Pin<Box<dyn Future<Output = O>>>>,
}
pub struct Submission<I, O>
where
I: 'static,
O: 'static,
{
pub input: RwSignal<Option<I>>,
pub value: RwSignal<Option<O>>,
pub(crate) pending: RwSignal<bool>,
pub canceled: RwSignal<bool>,
}
impl<I, O> Clone for Submission<I, O> {
fn clone(&self) -> Self {
Self {
input: self.input,
value: self.value,
pending: self.pending,
canceled: self.canceled,
}
}
}
impl<I, O> Copy for Submission<I, O> {}
impl<I, O> Submission<I, O>
where
I: 'static,
O: 'static,
{
pub fn pending(&self) -> ReadSignal<bool> {
self.pending.read_only()
}
pub fn cancel(&self) {
self.canceled.set(true);
}
}
impl<I, O> MultiActionState<I, O>
where
I: 'static,
O: 'static,
{
pub fn dispatch(&self, input: I) {
let cx = self.cx;
let fut = (self.action_fn)(&input);
let submission = Submission {
input: create_rw_signal(cx, Some(input)),
value: create_rw_signal(cx, None),
pending: create_rw_signal(cx, true),
canceled: create_rw_signal(cx, false),
};
self.submissions.update(|subs| subs.push(submission));
let canceled = submission.canceled;
let input = submission.input;
let pending = submission.pending;
let value = submission.value;
let version = self.version;
spawn_local(async move {
let new_value = fut.await;
let canceled = cx.untrack(move || canceled.get());
input.set(None);
pending.set(false);
if !canceled {
value.set(Some(new_value));
}
version.update(|n| *n += 1);
})
}
pub fn submissions(&self) -> ReadSignal<Vec<Submission<I, O>>> {
self.submissions.read_only()
}
}
pub fn create_multi_action<I, O, F, Fu>(cx: Scope, action_fn: F) -> MultiAction<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 submissions = create_rw_signal(cx, Vec::new());
let action_fn = Rc::new(move |input: &I| {
let fut = action_fn(input);
Box::pin(async move { fut.await }) as Pin<Box<dyn Future<Output = O>>>
});
MultiAction(store_value(
cx,
MultiActionState {
cx,
version,
submissions,
url: None,
action_fn,
},
))
}
pub fn create_server_multi_action<S>(cx: Scope) -> MultiAction<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_multi_action(cx, c).using_server_fn::<S>()
}