import React, { Component } from 'react'; import { PropTypes } from 'prop-types'; import { createStylingFromTheme, base16Themes, } from './utils/createStylingFromTheme'; import shouldPureComponentUpdate from 'react-pure-render/function'; import ActionList from './ActionList'; import ActionPreview from './ActionPreview'; import getInspectedState from './utils/getInspectedState'; import createDiffPatcher from './createDiffPatcher'; import { getBase16Theme, invertTheme } from 'react-base16-styling'; import { reducer, updateMonitorState } from './redux'; import { ActionCreators } from 'redux-devtools'; const { commit, sweep, toggleAction, jumpToAction, jumpToState, reorderAction, } = ActionCreators; function getLastActionId(props) { return props.stagedActionIds[props.stagedActionIds.length - 1]; } function getCurrentActionId(props, monitorState) { return monitorState.selectedActionId === null ? props.stagedActionIds[props.currentStateIndex] : monitorState.selectedActionId; } function getFromState( actionIndex, stagedActionIds, computedStates, monitorState ) { const { startActionId } = monitorState; if (startActionId === null) { return actionIndex > 0 ? computedStates[actionIndex - 1] : null; } let fromStateIdx = stagedActionIds.indexOf(startActionId - 1); if (fromStateIdx === -1) fromStateIdx = 0; return computedStates[fromStateIdx]; } function createIntermediateState(props, monitorState) { const { supportImmutable, computedStates, stagedActionIds, actionsById: actions, diffObjectHash, diffPropertyFilter, } = props; const { inspectedStatePath, inspectedActionPath } = monitorState; const currentActionId = getCurrentActionId(props, monitorState); const currentAction = actions[currentActionId] && actions[currentActionId].action; const actionIndex = stagedActionIds.indexOf(currentActionId); const fromState = getFromState( actionIndex, stagedActionIds, computedStates, monitorState ); const toState = computedStates[actionIndex]; const error = toState && toState.error; const fromInspectedState = !error && fromState && getInspectedState(fromState.state, inspectedStatePath, supportImmutable); const toInspectedState = !error && toState && getInspectedState(toState.state, inspectedStatePath, supportImmutable); const delta = !error && fromState && toState && createDiffPatcher(diffObjectHash, diffPropertyFilter).diff( fromInspectedState, toInspectedState ); return { delta, nextState: toState && getInspectedState(toState.state, inspectedStatePath, false), action: getInspectedState(currentAction, inspectedActionPath, false), error, }; } function createThemeState(props) { const base16Theme = getBase16Theme(props.theme, base16Themes); const theme = props.invertTheme ? invertTheme(props.theme) : props.theme; const styling = createStylingFromTheme(theme); return { base16Theme, styling }; } export default class DevtoolsInspector extends Component { constructor(props) { super(props); this.state = { ...createIntermediateState(props, props.monitorState), isWideLayout: false, themeState: createThemeState(props), }; } static propTypes = { dispatch: PropTypes.func, computedStates: PropTypes.array, stagedActionIds: PropTypes.array, actionsById: PropTypes.object, currentStateIndex: PropTypes.number, monitorState: PropTypes.shape({ initialScrollTop: PropTypes.number, }), preserveScrollTop: PropTypes.bool, draggableActions: PropTypes.bool, stagedActions: PropTypes.array, select: PropTypes.func.isRequired, theme: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), supportImmutable: PropTypes.bool, diffObjectHash: PropTypes.func, diffPropertyFilter: PropTypes.func, hideMainButtons: PropTypes.bool, hideActionButtons: PropTypes.bool, invertTheme: PropTypes.bool, skippedActionIds: PropTypes.array, dataTypeKey: PropTypes.any, tabs: PropTypes.oneOfType([PropTypes.array, PropTypes.func]), }; static update = reducer; static defaultProps = { select: (state) => state, supportImmutable: false, draggableActions: true, theme: 'inspector', invertTheme: true, }; shouldComponentUpdate = shouldPureComponentUpdate; componentDidMount() { this.updateSizeMode(); this.updateSizeTimeout = setInterval(this.updateSizeMode.bind(this), 150); } componentWillUnmount() { clearTimeout(this.updateSizeTimeout); } updateMonitorState = (monitorState) => { this.props.dispatch(updateMonitorState(monitorState)); }; updateSizeMode() { const isWideLayout = this.inspectorRef.offsetWidth > 500; if (isWideLayout !== this.state.isWideLayout) { this.setState({ isWideLayout }); } } UNSAFE_componentWillReceiveProps(nextProps) { let nextMonitorState = nextProps.monitorState; const monitorState = this.props.monitorState; if ( getCurrentActionId(this.props, monitorState) !== getCurrentActionId(nextProps, nextMonitorState) || monitorState.startActionId !== nextMonitorState.startActionId || monitorState.inspectedStatePath !== nextMonitorState.inspectedStatePath || monitorState.inspectedActionPath !== nextMonitorState.inspectedActionPath || this.props.computedStates !== nextProps.computedStates || this.props.stagedActionIds !== nextProps.stagedActionIds ) { this.setState(createIntermediateState(nextProps, nextMonitorState)); } if ( this.props.theme !== nextProps.theme || this.props.invertTheme !== nextProps.invertTheme ) { this.setState({ themeState: createThemeState(nextProps) }); } } inspectorCreateRef = (node) => { this.inspectorRef = node; }; render() { const { stagedActionIds: actionIds, actionsById: actions, computedStates, draggableActions, tabs, invertTheme, skippedActionIds, currentStateIndex, monitorState, dataTypeKey, hideMainButtons, hideActionButtons, } = this.props; const { selectedActionId, startActionId, searchValue, tabName, } = monitorState; const inspectedPathType = tabName === 'Action' ? 'inspectedActionPath' : 'inspectedStatePath'; const { themeState, isWideLayout, action, nextState, delta, error, } = this.state; const { base16Theme, styling } = themeState; return (