import React, { Component } from 'react'; import { Base16Theme } from 'redux-devtools-themes'; import { Action } from 'redux'; import type { LabelRenderer } from 'react-json-tree'; import { PerformAction } from '@redux-devtools/core'; import type { Delta } from 'jsondiffpatch'; import type { JSX } from '@emotion/react/jsx-runtime'; import { DEFAULT_STATE, DevtoolsInspectorState } from './redux'; import ActionPreviewHeader from './ActionPreviewHeader'; import DiffTab from './tabs/DiffTab'; import StateTab from './tabs/StateTab'; import ActionTab from './tabs/ActionTab'; import cloneDeep from 'lodash.clonedeep'; export interface TabComponentProps> { labelRenderer: LabelRenderer; computedStates: { state: S; error?: string }[]; actions: { [actionId: number]: PerformAction }; selectedActionId: number | null; startActionId: number | null; base16Theme: Base16Theme; invertTheme: boolean; isWideLayout: boolean; sortStateTreeAlphabetically: boolean; disableStateTreeCollection: boolean; dataTypeKey: string | symbol | undefined; delta: Delta | null | undefined | false; action: A; nextState: S; monitorState: DevtoolsInspectorState; updateMonitorState: (monitorState: Partial) => void; } export interface Tab> { name: string; component: React.ComponentType>; } const DEFAULT_TABS = [ { name: 'Action', component: ActionTab, }, { name: 'Diff', component: DiffTab, }, { name: 'State', component: StateTab, }, ]; interface Props> { base16Theme: Base16Theme; invertTheme: boolean; isWideLayout: boolean; tabs: Tab[] | ((tabs: Tab[]) => Tab[]); tabName: string; delta: Delta | null | undefined | false; error: string | undefined; nextState: S; computedStates: { state: S; error?: string }[]; action: A; actions: { [actionId: number]: PerformAction }; selectedActionId: number | null; startActionId: number | null; dataTypeKey: string | symbol | undefined; monitorState: DevtoolsInspectorState; updateMonitorState: (monitorState: Partial) => void; onInspectPath: (path: (string | number)[]) => void; inspectedPath: (string | number)[]; onSelectTab: (tabName: string) => void; sortStateTreeAlphabetically: boolean; disableStateTreeCollection: boolean; } class ActionPreview> extends Component< Props > { static defaultProps = { tabName: DEFAULT_STATE.tabName, }; copyToClipboard = () => { try { const deepCopiedObject = cloneDeep(this.props.nextState); const jsonString = JSON.stringify(deepCopiedObject, null, 2); void navigator.clipboard.writeText(jsonString); } catch (err) { console.error('Failed to copy: ', err); } }; render(): JSX.Element { const { delta, error, nextState, onInspectPath, inspectedPath, tabName, isWideLayout, onSelectTab, action, actions, selectedActionId, startActionId, computedStates, base16Theme, invertTheme, tabs, dataTypeKey, monitorState, updateMonitorState, sortStateTreeAlphabetically, disableStateTreeCollection, } = this.props; const renderedTabs: Tab[] = typeof tabs === 'function' ? tabs(DEFAULT_TABS as Tab[]) : tabs ? tabs : (DEFAULT_TABS as Tab[]); const { component: TabComponent } = renderedTabs.find((tab) => tab.name === tabName) || renderedTabs.find((tab) => tab.name === DEFAULT_STATE.tabName)!; return (
({ flex: 1, display: 'flex', flexDirection: 'column', flexGrow: 1, overflowY: 'hidden', '& pre': { border: 'inherit', borderRadius: '3px', lineHeight: 'inherit', color: 'inherit', }, backgroundColor: theme.BACKGROUND_COLOR, })} > >[]} {...{ inspectedPath, onInspectPath, tabName, onSelectTab }} /> {!error && (
)} {error && (
({ padding: '10px', marginLeft: '14px', fontWeight: 'bold', color: theme.ERROR_COLOR, })} > {error}
)}
); } labelRenderer: LabelRenderer = ([key, ...rest], nodeType, expanded) => { const { onInspectPath, inspectedPath } = this.props; return ( {key} ({ fontSize: '0.7em', paddingLeft: '5px', cursor: 'pointer', '&:hover': { textDecoration: 'underline', }, color: theme.PIN_COLOR, })} onClick={() => onInspectPath([ ...inspectedPath.slice(0, inspectedPath.length - 1), ...[key, ...rest].reverse(), ]) } > {'(pin)'} ({ fontSize: '0.7em', paddingLeft: '5px', cursor: 'pointer', '&:hover': { textDecoration: 'underline', }, color: theme.PIN_COLOR, })} onClick={event => { event.stopPropagation(); this.copyToClipboard(); }} > {'(copy)'} {!expanded && ': '} ); }; } export default ActionPreview;