use crate::{use_callback, use_signal};
use dioxus_core::{use_hook, Callback, CapturedError, Result, Task};
use dioxus_signals::{ReadSignal, ReadableBoxExt, ReadableExt, Signal, WritableExt};
use futures_channel::oneshot::Receiver;
use futures_util::{future::Shared, FutureExt};
use std::{marker::PhantomData, pin::Pin, prelude::rust_2024::Future, task::Poll};
pub fn use_action<E, C, M>(mut user_fn: C) -> Action<C::Input, C::Output>
where
E: Into<CapturedError> + 'static,
C: ActionCallback<M, E>,
M: 'static,
C::Input: 'static,
C::Output: 'static,
C: 'static,
{
let mut value = use_signal(|| None as Option<C::Output>);
let mut error = use_signal(|| None as Option<CapturedError>);
let mut task = use_signal(|| None as Option<Task>);
let mut state = use_signal(|| ActionState::Unset);
let callback = use_callback(move |input: C::Input| {
if let Some(task) = task.take() {
task.cancel();
}
let (tx, rx) = futures_channel::oneshot::channel();
let rx = rx.shared();
let result = user_fn.call(input);
let new_task = dioxus_core::spawn(async move {
state.set(ActionState::Pending);
let result = result.await;
match result {
Ok(res) => {
error.set(None);
value.set(Some(res));
state.set(ActionState::Ready);
}
Err(err) => {
error.set(Some(err.into()));
value.set(None);
state.set(ActionState::Errored);
}
}
tx.send(()).ok();
});
task.set(Some(new_task));
rx
});
let reader = use_hook(|| value.boxed().map(|v| v.as_ref().unwrap()).boxed());
Action {
value,
error,
task,
callback,
reader,
_phantom: PhantomData,
state,
}
}
pub struct Action<I, T: 'static> {
reader: ReadSignal<T>,
error: Signal<Option<CapturedError>>,
value: Signal<Option<T>>,
task: Signal<Option<Task>>,
callback: Callback<I, Shared<Receiver<()>>>,
state: Signal<ActionState>,
_phantom: PhantomData<*const I>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
enum ActionState {
Unset,
Pending,
Ready,
Errored,
Reset,
}
impl<I: 'static, O: 'static> Action<I, O> {
pub fn value(&self) -> Option<Result<ReadSignal<O>, CapturedError>> {
if !matches!(
*self.state.read(),
ActionState::Ready | ActionState::Errored
) {
return None;
}
if let Some(err) = self.error.cloned() {
return Some(Err(err));
}
if self.value.read().is_none() {
return None;
}
Some(Ok(self.reader))
}
pub fn pending(&self) -> bool {
*self.state.read() == ActionState::Pending
}
pub fn reset(&mut self) {
self.state.set(ActionState::Reset);
if let Some(t) = self.task.take() {
t.cancel()
}
}
pub fn cancel(&mut self) {
if let Some(t) = self.task.take() {
t.cancel()
}
self.state.set(ActionState::Reset);
}
}
impl<I, T> std::fmt::Debug for Action<I, T>
where
T: std::fmt::Debug + 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
f.debug_struct("Action")
.field("state", &self.state.read())
.field("value", &self.value.read())
.field("error", &self.error.read())
.finish()
} else {
std::fmt::Debug::fmt(&self.value.read().as_ref(), f)
}
}
}
impl<I, T> PartialEq for Action<I, T> {
fn eq(&self, other: &Self) -> bool {
self.callback == other.callback
}
}
pub struct Dispatching<I> {
_phantom: PhantomData<*const I>,
receiver: Shared<Receiver<()>>,
}
impl<T> Dispatching<T> {
pub(crate) fn new(receiver: Shared<Receiver<()>>) -> Self {
Self {
_phantom: PhantomData,
receiver,
}
}
}
impl<T> std::future::Future for Dispatching<T> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
match self.receiver.poll_unpin(_cx) {
Poll::Ready(_) => Poll::Ready(()),
Poll::Pending => Poll::Pending,
}
}
}
impl<I, T> Copy for Action<I, T> {}
impl<I, T> Clone for Action<I, T> {
fn clone(&self) -> Self {
*self
}
}
pub trait ActionCallback<Marker, Err> {
type Input;
type Output;
fn call(
&mut self,
input: Self::Input,
) -> impl Future<Output = Result<Self::Output, Err>> + 'static;
}
macro_rules! impl_action_callback {
() => {
impl<Func, Out, Fut, Err> ActionCallback<(Out,), Err> for Func
where
Func: FnMut() -> Fut,
Fut: Future<Output = Result<Out, Err>> + 'static,
{
type Input = ();
type Output = Out;
fn call(
&mut self,
_input: Self::Input,
) -> impl Future<Output = Result<Self::Output, Err>> + 'static {
(self)()
}
}
impl<Out> Action<(), Out> {
pub fn call(&mut self) -> Dispatching<()> {
Dispatching::new((self.callback).call(()))
}
}
};
($($arg:ident),+) => {
impl<Func, Out, $($arg,)+ Fut, Err> ActionCallback<($($arg,)+ Out), Err> for Func
where
Func: FnMut($($arg),+) -> Fut,
Fut: Future<Output = Result<Out, Err>> + 'static,
{
type Input = ($($arg,)+);
type Output = Out;
fn call(
&mut self,
input: Self::Input,
) -> impl Future<Output = Result<Self::Output, Err>> + 'static {
#[allow(non_snake_case)]
let ($($arg,)+) = input;
(self)($($arg),+)
}
}
impl<$($arg: 'static,)+ Out> Action<($($arg,)+), Out> {
#[allow(non_snake_case)]
pub fn call(&mut self, $($arg: $arg),+) -> Dispatching<()> {
Dispatching::new((self.callback).call(($($arg,)+)))
}
}
};
}
impl_action_callback!();
impl_action_callback!(A);
impl_action_callback!(A, B);
impl_action_callback!(A, B, C);
impl_action_callback!(A, B, C, D);
impl_action_callback!(A, B, C, D, E);
impl_action_callback!(A, B, C, D, E, F);