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 } 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 styling = createStylingFromTheme(props.theme, props.invertTheme); 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.string, 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 }); } } 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 (