redux-devtools-log-monitor-no-peers
Version:
The default tree view monitor for Redux DevTools
242 lines (214 loc) • 6.34 kB
JavaScript
import React, { PropTypes, Component } from 'react';
import LogMonitorEntry from './LogMonitorEntry';
import LogMonitorButton from './LogMonitorButton';
import shouldPureComponentUpdate from 'react-pure-render/function';
import * as themes from 'redux-devtools-themes';
import { ActionCreators } from 'redux-devtools';
import { updateScrollTop } from './actions';
import reducer from './reducers';
const { reset, rollback, commit, sweep, toggleAction } = ActionCreators;
const styles = {
container: {
fontFamily: 'monaco, Consolas, Lucida Console, monospace',
position: 'relative',
overflowY: 'hidden',
width: '100%',
height: '100%',
minWidth: 300,
direction: 'ltr'
},
buttonBar: {
textAlign: 'center',
borderBottomWidth: 1,
borderBottomStyle: 'solid',
borderColor: 'transparent',
zIndex: 1,
display: 'flex',
flexDirection: 'row'
},
elements: {
position: 'absolute',
left: 0,
right: 0,
top: 38,
bottom: 0,
overflowX: 'hidden',
overflowY: 'auto'
}
};
export default class LogMonitor extends Component {
static update = reducer;
static propTypes = {
dispatch: PropTypes.func,
computedStates: PropTypes.array,
actionsById: PropTypes.object,
stagedActionIds: PropTypes.array,
skippedActionIds: PropTypes.array,
monitorState: PropTypes.shape({
initialScrollTop: PropTypes.number
}),
preserveScrollTop: PropTypes.bool,
select: PropTypes.func.isRequired,
theme: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
expandActionRoot: PropTypes.bool,
expandStateRoot: PropTypes.bool
};
static defaultProps = {
select: (state) => state,
theme: 'nicinabox',
preserveScrollTop: true,
expandActionRoot: true,
expandStateRoot: true
};
shouldComponentUpdate = shouldPureComponentUpdate;
constructor(props) {
super(props);
this.handleToggleAction = this.handleToggleAction.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleRollback = this.handleRollback.bind(this);
this.handleSweep = this.handleSweep.bind(this);
this.handleCommit = this.handleCommit.bind(this);
}
scroll() {
const node = this.refs.container;
if (!node) {
return;
}
if (this.scrollDown) {
const { offsetHeight, scrollHeight } = node;
node.scrollTop = scrollHeight - offsetHeight;
this.scrollDown = false;
}
}
componentDidMount() {
const node = this.refs.container;
if (!node) {
return;
}
if (this.props.preserveScrollTop) {
node.scrollTop = this.props.monitorState.initialScrollTop;
this.interval = setInterval(::this.updateScrollTop, 1000);
} else {
this.scrollDown = true;
this.scroll();
}
}
componentWillUnmount() {
if (this.interval) {
clearInterval(this.interval);
}
}
updateScrollTop() {
const node = this.refs.container;
this.props.dispatch(updateScrollTop(node ? node.scrollTop : 0));
}
componentWillReceiveProps(nextProps) {
const node = this.refs.container;
if (!node) {
this.scrollDown = true;
} else if (
this.props.stagedActionIds.length <
nextProps.stagedActionIds.length
) {
const { scrollTop, offsetHeight, scrollHeight } = node;
this.scrollDown = Math.abs(
scrollHeight - (scrollTop + offsetHeight)
) < 20;
} else {
this.scrollDown = false;
}
}
componentDidUpdate() {
this.scroll();
}
handleRollback() {
this.props.dispatch(rollback());
}
handleSweep() {
this.props.dispatch(sweep());
}
handleCommit() {
this.props.dispatch(commit());
}
handleToggleAction(id) {
this.props.dispatch(toggleAction(id));
}
handleReset() {
this.props.dispatch(reset());
}
getTheme() {
let { theme } = this.props;
if (typeof theme !== 'string') {
return theme;
}
if (typeof themes[theme] !== 'undefined') {
return themes[theme];
}
console.warn('DevTools theme ' + theme + ' not found, defaulting to nicinabox');
return themes.nicinabox;
}
render() {
const elements = [];
const theme = this.getTheme();
const { actionsById, skippedActionIds, stagedActionIds, computedStates, select } = this.props;
for (let i = 0; i < stagedActionIds.length; i++) {
const actionId = stagedActionIds[i];
const action = actionsById[actionId].action;
const { state, error } = computedStates[i];
let previousState;
if (i > 0) {
previousState = computedStates[i - 1].state;
}
elements.push(
<LogMonitorEntry key={actionId}
theme={theme}
select={select}
action={action}
actionId={actionId}
state={state}
previousState={previousState}
collapsed={skippedActionIds.indexOf(actionId) > -1}
error={error}
expandActionRoot={this.props.expandActionRoot}
expandStateRoot={this.props.expandStateRoot}
onActionClick={this.handleToggleAction} />
);
}
return (
<div style={{...styles.container, backgroundColor: theme.base00}}>
<div style={{...styles.buttonBar, borderColor: theme.base02}}>
<LogMonitorButton
theme={theme}
onClick={this.handleReset}
enabled>
Reset
</LogMonitorButton>
<LogMonitorButton
theme={theme}
onClick={this.handleRollback}
enabled={computedStates.length > 1}>
Revert
</LogMonitorButton>
<LogMonitorButton
theme={theme}
onClick={this.handleSweep}
enabled={skippedActionIds.length > 0}>
Sweep
</LogMonitorButton>
<LogMonitorButton
theme={theme}
onClick={this.handleCommit}
enabled={computedStates.length > 1}>
Commit
</LogMonitorButton>
</div>
<div style={styles.elements} ref='container'>
{elements}
</div>
</div>
);
}
}