import { isObservableArray, isObservableMap, isObservableObject, isObservableSet, ObservableMap, ObservableSet, toJS, } from 'mobx'; import { toJsWithComputeds } from '../to-js-with-computeds'; type Css = { style: string }; type Chunk = [string, Css, string]; type Header = [string, Chunk, [string, string]]; type Body = [string, Css, ...any[]]; export type ChromeFormatter = { header: (argument: T) => Header | null; hasBody: (argument: T) => boolean; body: (argument: T) => Body; }; const styles = { mobxName: { style: 'color: rgb(232,98,0)' }, nullName: { style: 'color: #777' }, list: { style: 'list-style-type: none; padding: 0; margin: 0 0 0 12px; font-style: normal', }, }; const reference = (object: unknown) => { if (typeof object === 'undefined' || typeof object === null) { return ['span', styles.nullName, typeof object]; } return ['object', { object }]; }; const renderIterableHeader = ( iterable: | ArrayLike | ObservableMap | ObservableSet, name: string ): Header => { const count = isObservableMap(iterable) || isObservableSet(iterable) ? iterable.size : iterable.length; return ['span', ['span', styles.mobxName, name], ['span', `[${count}]`]]; }; const renderIterableBody = ( collection: | ArrayLike | ObservableMap | ObservableSet | {}, mapper: (val: [string, unknown]) => unknown ): Body => { if (isObservableMap(collection) || isObservableSet(collection)) { const children = Array.from(toJS(collection)).map(mapper); return ['ol', styles.list, ...children]; } const toJSResult = toJsWithComputeds(collection); const children = Object.entries(toJSResult).map(mapper); return ['ol', styles.list, ...children]; }; export class ObjectFormatter implements ChromeFormatter<{}> { header(argument: {}): Header | null { return isObservableObject(argument) ? renderIterableHeader( Object.keys(argument), argument?.constructor.name ?? 'Object' ) : null; } hasBody(argument: {}): boolean { return Object.keys(argument).length > 0; } body(argument: {}) { return renderIterableBody(argument, ([key, value]) => [ 'li', {}, reference(key), ': ', reference(value), ]); } } export class ArrayFormatter implements ChromeFormatter> { header(argument: ArrayLike): Header | null { return isObservableArray(argument) ? renderIterableHeader(argument, 'Array') : null; } hasBody(argument: ArrayLike): boolean { return argument.length > 0; } body(argument: ArrayLike): Body { return renderIterableBody(argument, ([_, value]) => [ 'li', reference(value), ]); } } export class MapFormatter implements ChromeFormatter> { header(argument: Map): Header | null { return isObservableMap(argument) ? renderIterableHeader(argument, 'Map') : null; } hasBody(argument: Map): boolean { return argument.size > 0; } body(argument: Map): Body { return renderIterableBody(argument, ([key, value]) => [ 'li', {}, reference(key), ': ', reference(value), ]); } } export class SetFormatter implements ChromeFormatter> { header(argument: Set): Header | null { return isObservableSet(argument) ? renderIterableHeader(argument, 'Set') : null; } hasBody(argument: Set): boolean { return argument.size > 0; } body(argument: Set): Body { return renderIterableBody(argument, (value) => ['li', reference(value)]); } } let isMobxFormattersInstalled = false; export const installMobxFormatters = () => { if (isMobxFormattersInstalled) { return; } const isBrowser = typeof window !== 'undefined'; if (isBrowser) { window.devtoolsFormatters = window.devtoolsFormatters || []; window.devtoolsFormatters.push( new ArrayFormatter(), new ObjectFormatter(), new MapFormatter(), new SetFormatter() ); isMobxFormattersInstalled = true; } };