import React from 'react'; import { connect } from 'react-redux'; import { hideHint } from 'Duck/components/player'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { JSONTree, NoContent, Tooltip } from 'UI'; import { formatMs } from 'App/date'; import { diff } from 'deep-diff'; import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player'; import Autoscroll from '../Autoscroll'; import BottomBlock from '../BottomBlock/index'; import DiffRow from './DiffRow'; import cn from 'classnames'; import stl from './storage.module.css'; function getActionsName(type: string) { switch (type) { case STORAGE_TYPES.MOBX: case STORAGE_TYPES.VUEX: return 'MUTATIONS'; default: return 'ACTIONS'; } } interface Props { hideHint: (args: string) => void; hintIsHidden: boolean; } function Storage(props: Props) { const lastBtnRef = React.useRef(); const [showDiffs, setShowDiffs] = React.useState(false); const { player, store } = React.useContext(PlayerContext); const state = store.get(); const listNow = selectStorageListNow(state); const list = selectStorageList(state); const type = selectStorageType(state); const focusNextButton = () => { if (lastBtnRef.current) { lastBtnRef.current.focus(); } }; React.useEffect(() => { focusNextButton(); }, []); React.useEffect(() => { focusNextButton(); }, [listNow]); const renderDiff = (item: Record, prevItem: Record) => { if (!prevItem) { // we don't have state before first action return
; } const stateDiff = diff(prevItem.state, item.state); if (!stateDiff) { return (
No diff
); } return (
{stateDiff.map((d: Record, i: number) => renderDiffs(d, i))}
); }; const renderDiffs = (diff: Record, i: number) => { const path = createPath(diff); return ( ); }; const createPath = (diff: Record) => { let path: string[] = []; if (diff.path) { path = path.concat(diff.path); } if (typeof diff.index !== 'undefined') { path.push(diff.index); } const pathStr = path.length ? path.join('.') : ''; return pathStr; }; const ensureString = (actionType: string) => { if (typeof actionType === 'string') return actionType; return 'UNKNOWN'; }; const goNext = () => { // , list[listNow.length]._index player.jump(list[listNow.length].time); }; const renderItem = (item: Record, i: number, prevItem: Record) => { let src; let name; switch (type) { case STORAGE_TYPES.REDUX: case STORAGE_TYPES.NGRX: src = item.action; name = src && src.type; break; case STORAGE_TYPES.VUEX: src = item.mutation; name = src && src.type; break; case STORAGE_TYPES.MOBX: src = item.payload; name = `@${item.type} ${src && src.type}`; break; case STORAGE_TYPES.ZUSTAND: src = null; name = item.mutation.join(''); } if (src !== null && !showDiffs) { setShowDiffs(true); } return (
{src === null ? (
{name}
) : ( <> {renderDiff(item, prevItem)}
)}
{typeof item.duration === 'number' && (
{formatMs(item.duration)}
)}
{i + 1 < listNow.length && ( )} {i + 1 === listNow.length && i + 1 < list.length && ( )}
); }; const { hintIsHidden } = props; const showStore = type !== STORAGE_TYPES.MOBX; return ( {list.length > 0 && (
{showStore && (

{'STATE'}

)} {showDiffs ? (

DIFFS

) : null}

{getActionsName(type)}

TTE

)}
{ 'Inspect your application state while you’re replaying your users sessions. OpenReplay supports ' } Redux {', '} VueX {', '} Pinia {', '} Zustand {', '} MobX {' and '} NgRx .

) : null } size="small" show={list.length === 0} > {showStore && (
{list.length === 0 ? (
{'Empty state.'}
) : ( )}
)}
{listNow.map((item: Record, i: number) => renderItem(item, i, i > 0 ? listNow[i - 1] : undefined) )}
); } export default connect( (state: any) => ({ hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']), }), { hideHint, } )(observer(Storage));