1use std::{rc::Rc, time::Duration};
2
3use dioxus::{prelude::*, web::WebEventExt};
4
5#[derive(Clone, PartialEq)]
6pub enum Operation {
7 Add(String),
8 Remove(String),
9 Group(Vec<Operation>),
10}
11
12fn operate(patient: &web_sys::Element, operation: &Operation) {
13 match operation {
14 Operation::Add(class) => {
15 let classes = class.split_whitespace().collect::<Vec<&str>>();
16
17 for class in classes {
18 let _ = patient.class_list().add_1(class);
19 }
20 }
21 Operation::Remove(class) => {
22 let classes = class.split_whitespace().collect::<Vec<&str>>();
23
24 for class in classes {
25 let _ = patient.class_list().remove_1(class);
26 }
27 },
28 Operation::Group(operations) => {
29 for operation in operations {
30 operate(patient, operation);
31 }
32 }
33 }
34}
35
36#[macro_export]
37macro_rules! use_animate {
38 ( $($input:tt)* ) => {{
39 let operations = use_signal(|| {
40 let mut ops = Vec::new();
41 $crate::__parse_animations!(ops, $($input)*);
42 ops
43 });
44
45 $crate::_use_animate(operations.into())
46 }};
47}
48
49#[macro_export]
50macro_rules! __parse_animations {
51 ($ops:ident, $t:expr => add($s:literal), $($rest:tt)*) => {
52 $ops.push(($t, $crate::Operation::Add($s.to_string())));
53 $crate::__parse_animations!($ops, $($rest)*);
54 };
55
56 ($ops:ident, $t:expr => remove($s:literal), $($rest:tt)*) => {
57 $ops.push(($t, $crate::Operation::Remove($s.to_string())));
58 $crate::__parse_animations!($ops, $($rest)*);
59 };
60
61 ($ops:ident, $t:expr => ($($inner:tt)*), $($rest:tt)*) => {
62 $ops.push(($t, {
63 let mut group_ops = Vec::new();
64 $crate::__expand_inner!(group_ops, $($inner)*);
65 $crate::Operation::Group(group_ops)
66 }));
67 $crate::__parse_animations!($ops, $($rest)*);
68 };
69
70 ($ops:ident, $t:expr => add($s:literal)) => {
71 $ops.push(($t, $crate::Operation::Add($s.to_string())));
72 };
73
74 ($ops:ident, $t:expr => remove($s:literal)) => {
75 $ops.push(($t, $crate::Operation::Remove($s.to_string())));
76 };
77
78 ($ops:ident, $t:expr => ($($inner:tt)*)) => {
79 $ops.push(($t, {
80 let mut group_ops = Vec::new();
81 $crate::__expand_inner!(group_ops, $($inner)*);
82 $crate::Operation::Group(group_ops)
83 }));
84 };
85
86 ($ops:ident,) => {};
87}
88
89#[macro_export]
90macro_rules! __expand_inner {
91 ( $ops:ident, add($s:literal); $($rest:tt)* ) => {
92 $ops.push($crate::Operation::Add($s.to_string()));
93 $crate::__expand_inner!($ops, $($rest)*);
94 };
95 ( $ops:ident, remove($s:literal); $($rest:tt)* ) => {
96 $ops.push($crate::Operation::Remove($s.to_string()));
97 $crate::__expand_inner!($ops, $($rest)*);
98 };
99 ( $ops:ident, add($s:literal) ) => {
100 $ops.push($crate::Operation::Add($s.to_string()));
101 };
102 ( $ops:ident, remove($s:literal) ) => {
103 $ops.push($crate::Operation::Remove($s.to_string()));
104 };
105 ( $ops:ident, ) => {};
106}
107
108pub struct UseAnimate {
109 ops: Vec<(u64, Operation)>
110}
111
112impl UseAnimate {
113 pub fn start(&self, patient: ReadOnlySignal<Option<Rc<MountedData>>>) {
114 let ops = self.ops.clone();
115
116 spawn(async move {
117 let Some(element) = patient() else {
118 return;
119 };
120
121 let mut time_elapsed = 0;
122
123 for (duration, operation) in ops.into_iter() {
124 let interval = duration - time_elapsed;
125
126 dioxus_time::sleep(Duration::from_millis(interval)).await;
127 time_elapsed += interval;
128
129 let web_element = element.as_web_event();
130 operate(&web_element, &operation);
131 }
132 });
133 }
134}
135
136pub fn _use_animate(ops: ReadOnlySignal<Vec<(u64, Operation)>>) -> UseAnimate {
137 UseAnimate {
138 ops: ops()
139 }
140}
141
142pub mod prelude {
143 #[macro_use]
144 pub use crate::use_animate;
145}