UNPKG

unstated-enhancers

Version:
297 lines (222 loc) 7.83 kB
import * as differ from 'deep-object-diff' import * as unstated from 'unstated' import Str from './support/Str' import LoggerConfig from './support/LoggerConfig' import colors from './support/logger-colors' import Counter from './support/counter' import polyfill from './support/console'; const _global: any = window || global; const __SUPER_SECRET_CONTAINER_DEBUG_HOOK__ = (unstated as any).__SUPER_SECRET_CONTAINER_DEBUG_HOOK__ const ENABLED = typeof _global !== 'undefined' && process.env.NODE_ENV !== 'production' && _global.__REDUX_DEVTOOLS_EXTENSION__ const console = polyfill(_global && _global.console ? _global.console : {}); if (ENABLED) { console.log(`You're currently using the unstated logger for development, disable it for production`) } else { console.log(`You're in production or redux dev tools plugin is missing so unstated logger is disabled`) } class Logger { collapsed: boolean debounce: number detailed: boolean enabled: boolean beauty: boolean private __containers: any private __devtool: any logger: any colors: Object details: string[] ignore: string[] private __widgets: Number private counter: Counter constructor() { this.colors = colors this.collapsed = false this.detailed = true this.enabled = ENABLED; this.beauty = false; this.logger = console this.ignore = ['JUMP_TO_STATE', 'JUMP_TO_ACTION'] this.details = ['added', 'updated', 'deleted'] this.__containers = {} this.__widgets = 1 this.debounce = 200; this.counter = new Counter(); this.counter.config({ collapsed: this.collapsed, debounce: this.debounce }); } __default() { if (this.enabled) { const name = 'Unstated logger'; this.__devtool = _global.__REDUX_DEVTOOLS_EXTENSION__.connect({ name }) this.__devtool.init() } } config(config: LoggerConfig) { for (const [key, value] of Object.entries(config)) { this[key] = value; } this.counter.config({ collapsed: this.collapsed, debounce: this.debounce }); this.enabled = this.enabled && _global.__REDUX_DEVTOOLS_EXTENSION__; } count(component: any | string) { if (this.enabled) { this.counter.count(component); } } dispatch(action, payload = undefined) { if (this.enabled) { const info = 'Dispatched'; const group = this.collapsed ? console.groupCollapsed : console.group const titleStyle = ['color: gray; font-weight: lighter;', 'color: #C2185B; font-weight: bold;'] group(`%c ${info} - %c${action}`, ...titleStyle) const contentStyle = `color: #455A64; font-weight: bold` if (payload !== undefined) { console.log('%c DATA:', contentStyle, payload) } else { console.log('%c NO DATA', contentStyle) } console.groupEnd() this.__devtool.send(`${info} - ${action}`, payload) } } store() { const store: any = {} for (const [key, value] of Object.entries(this.__containers)) { store[key] = (value as any).container.state } return store } print() { const color = (this.colors as any).title const style = `color: ${color}; font-weight: bold` for (const [key, value] of Object.entries(this.__containers)) { console.log(`%c ${key} → `, style, (value as any).container.state) } } private stateWasChanged(state: Object) { return Object.keys(state).length > 0 } private render(kind: string, value: Object) { const colors = this.colors as any const color = colors[kind].color const text = colors[kind].text const style = `color: ${color}; font-weight: bold` console.log(`%c → ${text}`, style, value) } start(config?: LoggerConfig) { if (config) { this.config(config); } this.__default() if (this.enabled) { __SUPER_SECRET_CONTAINER_DEBUG_HOOK__((container: any) => { let prevState = container.state || container.constructor.state let name = container.name || container.constructor.name if (this.beauty) { name = name.replace(/container$/ig, ''); name = Str.title(Str.uncamel(name)); } if (!this.__containers[name]) { this.connect( name, container ) } container.subscribe(() => { const state = { ...container.state } delete container.state.__action; this.logToConsole(this.__containers[name], prevState, state) this.emitStateChangesToReduxDevTools(this.__containers[name], state) prevState = state }) }) } } private connect(widget: string, container: any): void { let instance: any const name = widget || 'Unstated ' + this.__widgets container.__name = name this.__containers[name] = { container, instance } if (this.enabled) { instance = _global.__REDUX_DEVTOOLS_EXTENSION__.connect({ name }) instance.init() this.__containers[name].instance = instance this.subscribeToReduxDevToolsEvents(this.__containers[name]) } } private logToConsole(container: any, prevState: any, state: any) { const nextState = Object.assign({}, state) let name = container.container.__name const colors = this.colors as any const jump = container.container.__jump const group = this.collapsed ? console.groupCollapsed : console.group const titleStyle = ['color: gray; font-weight: lighter;', 'color: black; font-weight: bold;'] const info = this.getInfoForReduxDevTools(name, state) + (jump ? ' → ' + jump : '') const segments = info.split(' - ') group(`%c ${segments[0]} - %c ${segments[1]}`, ...titleStyle) const stylesPrevState = `color: ${colors.prevState.color}; font-weight: bold` const oldState = Object.assign({}, prevState) delete oldState.__action console.log('%c PREV STATE:', stylesPrevState, oldState) if (this.detailed) { const diff = (differ as any).detailedDiff(prevState, state) for (const i in this.details) { const kind = this.details[i] const value = Object.assign({}, diff[kind]) delete value.__action if (this.stateWasChanged(value)) { this.render(kind, value) } } } delete nextState.__action container.container.__jump = '' const stylesNextState = `color: ${colors.nextState.color}; font-weight: bold` console.log('%c NEXT STATE:', stylesNextState, nextState) console.groupEnd() } private subscribeToReduxDevToolsEvents(container: any): void { container.instance.subscribe((message: any) => { switch (message.payload && message.payload.type) { case 'JUMP_TO_STATE': case 'JUMP_TO_ACTION': const newState = JSON.parse(message.state) const action = { __action: message.payload.type } container.container.__jump = newState.__action const state = Object.assign({}, newState, action) container.container.setState(state) } }) } private getInfoForReduxDevTools(name: string, nextState: any): string { let info = '...' if (typeof nextState === 'object') { info = '' + nextState.__action info = name + ' - ' + info delete nextState.__jump } return info } private emitStateChangesToReduxDevTools(container: any, state: any): void { if (container.instance) { const name = container.container.__name const action = '' + state.__action const ignored = this.ignore.indexOf(action) !== -1 if (!ignored) { const info = this.getInfoForReduxDevTools(name, state) container.instance.send(info, state) } } } } export default Logger