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