use super::{Middleware, ReduceMiddlewareResult};
use crate::StoreEvent;
use serde::Serialize;
use std::{fmt::Display, hash::Hash};
use wasm_bindgen::JsValue;
use web_sys::console;
pub enum LogLevel {
Trace,
Debug,
Warn,
Info,
Log,
}
impl LogLevel {
pub fn log_1(&self, message: &JsValue) {
#[allow(unused_unsafe)]
unsafe {
match self {
LogLevel::Trace => console::trace_1(message),
LogLevel::Debug => console::debug_1(message),
LogLevel::Warn => console::warn_1(message),
LogLevel::Info => console::info_1(message),
LogLevel::Log => console::log_1(message),
}
}
}
pub fn log(&self, messages: Vec<JsValue>) {
let messages_array = js_sys::Array::new_with_length(messages.len() as u32);
for (i, m) in messages.into_iter().enumerate() {
messages_array.set(i as u32, m);
}
#[allow(unused_unsafe)]
unsafe {
match self {
LogLevel::Trace => console::trace(&messages_array),
LogLevel::Debug => console::debug(&messages_array),
LogLevel::Warn => console::warn(&messages_array),
LogLevel::Info => console::info(&messages_array),
LogLevel::Log => console::log(&messages_array),
}
}
}
}
pub enum DisplayType {
Groups,
SingleObject,
}
impl Default for DisplayType {
fn default() -> Self {
Self::Groups
}
}
#[derive(Serialize)]
struct OnReduceLog<'a, State, Action, Effect> {
action: &'a Option<Action>,
prev_state: &'a State,
next_state: &'a State,
effects: &'a [Effect],
}
#[derive(Serialize)]
struct OnNotifyLog<'a, State, Event> {
state: &'a State,
events: &'a [Event],
}
impl Default for LogLevel {
fn default() -> Self {
LogLevel::Log
}
}
pub struct WebLoggerMiddleware {
log_level: LogLevel,
display_type: DisplayType,
}
impl WebLoggerMiddleware {
pub fn new() -> Self {
Self {
log_level: LogLevel::default(),
display_type: DisplayType::default(),
}
}
pub fn log_level(mut self, log_level: LogLevel) -> Self {
self.log_level = log_level;
self
}
pub fn display_type(mut self, display_type: DisplayType) -> Self {
self.display_type = display_type;
self
}
fn on_reduce_groups<State, Action, Event, Effect>(
&self,
store: &crate::Store<State, Action, Event, Effect>,
action: Option<&Action>,
reduce: super::ReduceFn<State, Action, Event, Effect>,
) -> ReduceMiddlewareResult<Event, Effect>
where
State: Serialize,
Action: Serialize + Display,
Event: StoreEvent + Clone + Hash + Eq + Serialize,
Effect: Serialize,
{
let prev_state_js = JsValue::from_serde(&(*store.state())).unwrap();
let action_js = JsValue::from_serde(&action).unwrap();
let action_display = match &action {
Some(action) => format!("{}", action),
None => "None".to_string(),
};
let result = reduce(store, action);
let next_state_js = JsValue::from_serde(&(*store.state())).unwrap();
let effects_js = JsValue::from_serde(&result.effects).unwrap();
let effects_display = match &result.effects.len() {
0 => "None".to_string(),
_ => format!("({})", result.effects.len()),
};
#[allow(unused_unsafe)]
unsafe {
console::group_collapsed_3(
&JsValue::from_serde(&format!("%caction %c{}", action_display)).unwrap(),
&JsValue::from_str("color: gray; font-weight: lighter;"),
&JsValue::from_str("inherit"),
);
console::group_collapsed_2(
&JsValue::from_str("%cprev state"),
&JsValue::from_str("color: #9E9E9E; font-weight: bold;"),
);
}
self.log_level.log_1(&prev_state_js);
#[allow(unused_unsafe)]
unsafe {
console::group_end();
console::group_collapsed_3(
&JsValue::from_str(&format!("%caction: %c{}", action_display)),
&JsValue::from_str("color: #03A9F4; font-weight: bold;"),
&JsValue::from_str("color: gray; font-weight: lighter;"),
);
}
self.log_level.log_1(&action_js);
#[allow(unused_unsafe)]
unsafe {
console::group_end();
console::group_collapsed_2(
&JsValue::from_str("%cnext state"),
&JsValue::from_str("color: #4CAF50; font-weight: bold;"),
);
}
self.log_level.log_1(&next_state_js);
#[allow(unused_unsafe)]
unsafe {
console::group_end();
console::group_collapsed_3(
&JsValue::from_str(&format!("%ceffects: %c{}", effects_display)),
&JsValue::from_str("color: #C210C2; font-weight: bold;"),
&JsValue::from_str("color: gray; font-weight: lighter;"),
);
}
self.log_level.log_1(&effects_js);
#[allow(unused_unsafe)]
unsafe {
console::group_end();
}
result
}
fn on_reduce_no_groups<State, Action, Event, Effect>(
&self,
store: &crate::Store<State, Action, Event, Effect>,
action: Option<&Action>,
reduce: super::ReduceFn<State, Action, Event, Effect>,
) -> ReduceMiddlewareResult<Event, Effect>
where
State: Serialize,
Action: Serialize + Display,
Event: StoreEvent + Clone + Hash + Eq + Serialize,
Effect: Serialize,
{
let action_display = format!(
"on_reduce(), action: {}",
match &action {
Some(action) => format!("{}", action),
None => "None".to_string(),
}
);
let action_display_js = JsValue::from_str(&action_display);
let prev_state = store.state();
let result = reduce(store, action);
let next_state = store.state();
let log_object = OnReduceLog {
action: &action,
prev_state: &*prev_state,
next_state: &*next_state,
effects: &result.effects,
};
let log_object_js = JsValue::from_serde(&log_object).unwrap();
self.log_level.log(vec![action_display_js, log_object_js]);
result
}
fn on_notify_groups<State, Action, Event, Effect>(
&self,
store: &crate::Store<State, Action, Event, Effect>,
events: Vec<Event>,
notify: super::NotifyFn<State, Action, Event, Effect>,
) -> Vec<Event>
where
Event: Serialize,
{
let events_js = JsValue::from_serde(&events).unwrap();
let events_display = match events.len() {
0 => "None".to_string(),
_ => format!("({})", events.len()),
};
#[allow(unused_unsafe)]
unsafe {
console::group_collapsed_3(
&JsValue::from_str(&format!("%cevents: %c{}", events_display)),
&JsValue::from_str("color: #FCBA03; font-weight: bold;"),
&JsValue::from_str("color: gray; font-weight: lighter;"),
);
}
self.log_level.log_1(&events_js);
#[allow(unused_unsafe)]
unsafe {
console::group_end();
console::group_end();
}
notify(store, events)
}
fn on_notify_no_groups<State, Action, Event, Effect>(
&self,
store: &crate::Store<State, Action, Event, Effect>,
events: Vec<Event>,
notify: super::NotifyFn<State, Action, Event, Effect>,
) -> Vec<Event>
where
Event: Serialize + StoreEvent + Clone + Hash + Eq,
State: Serialize,
{
let log_object = OnNotifyLog {
state: &*store.state(),
events: &events,
};
let log_object_js = JsValue::from_serde(&log_object).unwrap();
let display = JsValue::from_str("on_notify(): ");
self.log_level.log(vec![display, log_object_js]);
notify(store, events)
}
}
impl Default for WebLoggerMiddleware {
fn default() -> Self {
WebLoggerMiddleware::new()
}
}
impl<State, Action, Event, Effect> Middleware<State, Action, Event, Effect> for WebLoggerMiddleware
where
State: Serialize,
Action: Serialize + Display,
Event: StoreEvent + Clone + Hash + Eq + Serialize,
Effect: Serialize,
{
fn on_reduce(
&self,
store: &crate::Store<State, Action, Event, Effect>,
action: Option<&Action>,
reduce: super::ReduceFn<State, Action, Event, Effect>,
) -> ReduceMiddlewareResult<Event, Effect> {
match self.display_type {
DisplayType::Groups => self.on_reduce_groups(store, action, reduce),
DisplayType::SingleObject => self.on_reduce_no_groups(store, action, reduce),
}
}
fn process_effect(
&self,
_store: &crate::Store<State, Action, Event, Effect>,
effect: Effect,
) -> Option<Effect> {
Some(effect)
}
fn on_notify(
&self,
store: &crate::Store<State, Action, Event, Effect>,
events: Vec<Event>,
notify: super::NotifyFn<State, Action, Event, Effect>,
) -> Vec<Event> {
match self.display_type {
DisplayType::Groups => self.on_notify_groups(store, events, notify),
DisplayType::SingleObject => self.on_notify_no_groups(store, events, notify),
}
}
}