use std::{future::Future, pin::Pin};
use topcoat::{Result, context::Cx};
use super::Resource;
pub trait Action<R: Resource>: 'static {
const NAME: &'static str;
const ROW: bool = true;
const BULK: bool = true;
const CONFIRM: bool = false;
fn label(cx: &Cx) -> String;
fn can_run(_cx: &Cx, _record: &R::Model) -> bool {
true
}
fn run(
cx: &Cx,
records: &[R::Model],
ex: &mut dyn toasty::Executor,
) -> impl Future<Output = Result<()>> + Send;
fn success(cx: &Cx, count: usize) -> String {
let noun = if count == 1 { "record" } else { "records" };
format!("{}: {count} {noun}", Self::label(cx))
}
}
pub(crate) type ActionFuture<'a> = Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
pub(crate) struct Actions<R: Resource> {
entries: Vec<ActionEntry<R>>,
}
impl<R: Resource> Default for Actions<R> {
fn default() -> Self {
Self {
entries: Vec::new(),
}
}
}
impl<R: Resource> std::fmt::Debug for Actions<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list()
.entries(self.entries.iter().map(|e| e.name))
.finish()
}
}
impl<R: Resource> Actions<R> {
pub(crate) fn add<A: Action<R>>(mut self) -> Self {
const {
assert!(
crate::declaration::segment_fault(A::NAME).is_none(),
"`Action::NAME` must be a single path segment"
);
}
self.entries.push(ActionEntry {
name: A::NAME,
label: A::label,
row: A::ROW,
bulk: A::BULK,
can_run: A::can_run,
run: run_erased::<R, A>,
success: A::success,
acted: super::Committed::acted::<R, A>,
confirm: A::CONFIRM,
});
self
}
pub(crate) fn entries(&self) -> &[ActionEntry<R>] {
&self.entries
}
pub(crate) fn find(&self, name: &str) -> Option<&ActionEntry<R>> {
self.entries.iter().find(|e| e.name == name)
}
}
pub(crate) struct ActionEntry<R: Resource> {
pub(crate) name: &'static str,
pub(crate) label: fn(&Cx) -> String,
pub(crate) row: bool,
pub(crate) bulk: bool,
pub(crate) can_run: fn(&Cx, &R::Model) -> bool,
pub(crate) run:
for<'a> fn(&'a Cx, &'a [R::Model], &'a mut dyn toasty::Executor) -> ActionFuture<'a>,
pub(crate) success: fn(&Cx, usize) -> String,
pub(crate) acted: fn(Vec<R::Model>) -> super::Committed<R::Model>,
pub(crate) confirm: bool,
}
fn run_erased<'a, R: Resource, A: Action<R>>(
cx: &'a Cx,
records: &'a [R::Model],
ex: &'a mut dyn toasty::Executor,
) -> ActionFuture<'a> {
Box::pin(A::run(cx, records, ex))
}