1use super::{Middleware, ReduceMiddlewareResult};
7use serde::Serialize;
8use std::{fmt::Display, hash::Hash};
9use wasm_bindgen::JsValue;
10use web_sys::console;
11
12pub enum LogLevel {
13 Trace,
14 Debug,
15 Warn,
16 Info,
17 Log,
18}
19
20impl LogLevel {
21 pub fn log_1(&self, message: &JsValue) {
22 #[allow(unused_unsafe)]
23 unsafe {
24 match self {
25 LogLevel::Trace => console::trace_1(message),
26 LogLevel::Debug => console::debug_1(message),
27 LogLevel::Warn => console::warn_1(message),
28 LogLevel::Info => console::info_1(message),
29 LogLevel::Log => console::log_1(message),
30 }
31 }
32 }
33
34 pub fn log(&self, messages: Vec<JsValue>) {
35 let messages_array = js_sys::Array::new_with_length(messages.len() as u32);
36
37 for (i, m) in messages.into_iter().enumerate() {
38 messages_array.set(i as u32, m);
39 }
40
41 #[allow(unused_unsafe)]
42 unsafe {
43 match self {
44 LogLevel::Trace => console::trace(&messages_array),
45 LogLevel::Debug => console::debug(&messages_array),
46 LogLevel::Warn => console::warn(&messages_array),
47 LogLevel::Info => console::info(&messages_array),
48 LogLevel::Log => console::log(&messages_array),
49 }
50 }
51 }
52}
53
54pub enum DisplayType {
55 Groups,
59 SingleObject,
61}
62
63impl Default for DisplayType {
64 fn default() -> Self {
65 Self::Groups
66 }
67}
68
69#[derive(Serialize)]
70struct OnReduceLog<'a, State, Action, Effect> {
71 action: &'a Option<Action>,
72 prev_state: &'a State,
73 next_state: &'a State,
74 effects: &'a [Effect],
75}
76
77#[derive(Serialize)]
78struct OnNotifyLog<'a, State, Event> {
79 state: &'a State,
80 events: &'a [Event],
81}
82
83impl Default for LogLevel {
84 fn default() -> Self {
85 LogLevel::Log
86 }
87}
88
89pub struct WebLoggerMiddleware {
93 log_level: LogLevel,
94 display_type: DisplayType,
95}
96
97impl WebLoggerMiddleware {
98 pub fn new() -> Self {
99 Self {
100 log_level: LogLevel::default(),
101 display_type: DisplayType::default(),
102 }
103 }
104
105 pub fn log_level(mut self, log_level: LogLevel) -> Self {
108 self.log_level = log_level;
109 self
110 }
111
112 pub fn display_type(mut self, display_type: DisplayType) -> Self {
115 self.display_type = display_type;
116 self
117 }
118
119 fn on_reduce_groups<State, Action, Event, Effect>(
120 &self,
121 store: &crate::Store<State, Action, Event, Effect>,
122 action: Option<&Action>,
123 reduce: super::ReduceFn<State, Action, Event, Effect>,
124 ) -> ReduceMiddlewareResult<Event, Effect>
125 where
126 State: Serialize,
127 Action: Serialize + Display,
128 Event: Clone + Hash + Eq + Serialize,
129 Effect: Serialize,
130 {
131 let prev_state_js = JsValue::from_serde(&(*store.state())).unwrap();
132
133 let action_js = JsValue::from_serde(&action).unwrap();
134 let action_display = match &action {
135 Some(action) => format!("{}", action),
136 None => "None".to_string(),
137 };
138
139 let result = reduce(store, action);
140 let next_state_js = JsValue::from_serde(&(*store.state())).unwrap();
141
142 let effects_js = JsValue::from_serde(&result.effects).unwrap();
143 let effects_display = match &result.effects.len() {
144 0 => "None".to_string(),
145 _ => format!("({})", result.effects.len()),
146 };
147
148 #[allow(unused_unsafe)]
149 unsafe {
150 console::group_collapsed_3(
151 &JsValue::from_serde(&format!("%caction %c{}", action_display)).unwrap(),
152 &JsValue::from_str("color: gray; font-weight: lighter;"),
153 &JsValue::from_str("inherit"),
154 );
155 console::group_collapsed_2(
156 &JsValue::from_str("%cprev state"),
157 &JsValue::from_str("color: #9E9E9E; font-weight: bold;"),
158 );
159 }
160
161 self.log_level.log_1(&prev_state_js);
162
163 #[allow(unused_unsafe)]
164 unsafe {
165 console::group_end();
166
167 console::group_collapsed_3(
168 &JsValue::from_str(&format!("%caction: %c{}", action_display)),
169 &JsValue::from_str("color: #03A9F4; font-weight: bold;"),
170 &JsValue::from_str("color: gray; font-weight: lighter;"),
171 );
172 }
173
174 self.log_level.log_1(&action_js);
175
176 #[allow(unused_unsafe)]
177 unsafe {
178 console::group_end();
179
180 console::group_collapsed_2(
181 &JsValue::from_str("%cnext state"),
182 &JsValue::from_str("color: #4CAF50; font-weight: bold;"),
183 );
184 }
185
186 self.log_level.log_1(&next_state_js);
187
188 #[allow(unused_unsafe)]
189 unsafe {
190 console::group_end();
191
192 console::group_collapsed_3(
193 &JsValue::from_str(&format!("%ceffects: %c{}", effects_display)),
194 &JsValue::from_str("color: #C210C2; font-weight: bold;"),
195 &JsValue::from_str("color: gray; font-weight: lighter;"),
196 );
197 }
198 self.log_level.log_1(&effects_js);
199
200 #[allow(unused_unsafe)]
201 unsafe {
202 console::group_end();
203 }
204
205 result
206 }
207
208 fn on_reduce_no_groups<State, Action, Event, Effect>(
209 &self,
210 store: &crate::Store<State, Action, Event, Effect>,
211 action: Option<&Action>,
212 reduce: super::ReduceFn<State, Action, Event, Effect>,
213 ) -> ReduceMiddlewareResult<Event, Effect>
214 where
215 State: Serialize,
216 Action: Serialize + Display,
217 Event: Clone + Hash + Eq + Serialize,
218 Effect: Serialize,
219 {
220 let action_display = format!(
221 "on_reduce(), action: {}",
222 match &action {
223 Some(action) => format!("{}", action),
224 None => "None".to_string(),
225 }
226 );
227
228 let action_display_js = JsValue::from_str(&action_display);
229
230 let prev_state = store.state();
231
232 let result = reduce(store, action);
233 let next_state = store.state();
234
235 let log_object = OnReduceLog {
236 action: &action,
237 prev_state: &*prev_state,
238 next_state: &*next_state,
239 effects: &result.effects,
240 };
241
242 let log_object_js = JsValue::from_serde(&log_object).unwrap();
243 self.log_level.log(vec![action_display_js, log_object_js]);
244
245 result
246 }
247
248 fn on_notify_groups<State, Action, Event, Effect>(
249 &self,
250 store: &crate::Store<State, Action, Event, Effect>,
251 events: Vec<Event>,
252 notify: super::NotifyFn<State, Action, Event, Effect>,
253 ) -> Vec<Event>
254 where
255 Event: Serialize,
256 {
257 let events_js = JsValue::from_serde(&events).unwrap();
258 let events_display = match events.len() {
259 0 => "None".to_string(),
260 _ => format!("({})", events.len()),
261 };
262
263 #[allow(unused_unsafe)]
264 unsafe {
265 console::group_collapsed_3(
266 &JsValue::from_str(&format!("%cevents: %c{}", events_display)),
267 &JsValue::from_str("color: #FCBA03; font-weight: bold;"),
268 &JsValue::from_str("color: gray; font-weight: lighter;"),
269 );
270 }
271
272 self.log_level.log_1(&events_js);
273
274 #[allow(unused_unsafe)]
275 unsafe {
276 console::group_end();
277 console::group_end();
278 }
279
280 notify(store, events)
281 }
282
283 fn on_notify_no_groups<State, Action, Event, Effect>(
284 &self,
285 store: &crate::Store<State, Action, Event, Effect>,
286 events: Vec<Event>,
287 notify: super::NotifyFn<State, Action, Event, Effect>,
288 ) -> Vec<Event>
289 where
290 Event: Serialize + Clone + Hash + Eq,
291 State: Serialize,
292 {
293 let log_object = OnNotifyLog {
294 state: &*store.state(),
295 events: &events,
296 };
297
298 let log_object_js = JsValue::from_serde(&log_object).unwrap();
299
300 let display = JsValue::from_str("on_notify(): ");
301
302 self.log_level.log(vec![display, log_object_js]);
303
304 notify(store, events)
305 }
306}
307
308impl Default for WebLoggerMiddleware {
309 fn default() -> Self {
310 WebLoggerMiddleware::new()
311 }
312}
313
314impl<State, Action, Event, Effect> Middleware<State, Action, Event, Effect> for WebLoggerMiddleware
315where
316 State: Serialize,
317 Action: Serialize + Display,
318 Event: Clone + Hash + Eq + Serialize,
319 Effect: Serialize,
320{
321 fn on_reduce(
322 &self,
323 store: &crate::Store<State, Action, Event, Effect>,
324 action: Option<&Action>,
325 reduce: super::ReduceFn<State, Action, Event, Effect>,
326 ) -> ReduceMiddlewareResult<Event, Effect> {
327 match self.display_type {
328 DisplayType::Groups => self.on_reduce_groups(store, action, reduce),
329 DisplayType::SingleObject => self.on_reduce_no_groups(store, action, reduce),
330 }
331 }
332
333 fn process_effect(
334 &self,
335 _store: &crate::Store<State, Action, Event, Effect>,
336 effect: Effect,
337 ) -> Option<Effect> {
338 Some(effect)
339 }
340
341 fn on_notify(
342 &self,
343 store: &crate::Store<State, Action, Event, Effect>,
344 events: Vec<Event>,
345 notify: super::NotifyFn<State, Action, Event, Effect>,
346 ) -> Vec<Event> {
347 match self.display_type {
348 DisplayType::Groups => self.on_notify_groups(store, events, notify),
349 DisplayType::SingleObject => self.on_notify_no_groups(store, events, notify),
350 }
351 }
352}