mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-25 11:03:57 +03:00
Merge redux-devtools-log-monitor (#432)
* Move from gaearon/redux-devtools-log-monitor * Npm package config and add credits
This commit is contained in:
parent
89880265a6
commit
e6fdfb9c9e
|
@ -12,6 +12,8 @@
|
||||||
"version": "npm run build:demo && git add -A .",
|
"version": "npm run build:demo && git add -A .",
|
||||||
"postversion": "git push",
|
"postversion": "git push",
|
||||||
"prepublish": "npm run build:lib",
|
"prepublish": "npm run build:lib",
|
||||||
|
"prepare": "npm run build:lib",
|
||||||
|
"prepublishOnly": "npm run lint && npm run build:lib",
|
||||||
"gh": "git subtree push --prefix demo/dist origin gh-pages"
|
"gh": "git subtree push --prefix demo/dist origin gh-pages"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
|
3
packages/redux-devtools-log-monitor/.babelrc
Normal file
3
packages/redux-devtools-log-monitor/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015-loose", "stage-0", "react"]
|
||||||
|
}
|
2
packages/redux-devtools-log-monitor/.eslintignore
Normal file
2
packages/redux-devtools-log-monitor/.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
lib
|
||||||
|
**/node_modules
|
20
packages/redux-devtools-log-monitor/.eslintrc
Normal file
20
packages/redux-devtools-log-monitor/.eslintrc
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"extends": "eslint-config-airbnb",
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"mocha": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-uses-react": 2,
|
||||||
|
"react/jsx-uses-vars": 2,
|
||||||
|
"react/react-in-jsx-scope": 2,
|
||||||
|
"no-console": 0,
|
||||||
|
// Temporarily disabled due to babel-eslint issues:
|
||||||
|
"block-scoped-var": 0,
|
||||||
|
"padded-blocks": 0,
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
}
|
21
packages/redux-devtools-log-monitor/LICENSE.md
Normal file
21
packages/redux-devtools-log-monitor/LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Dan Abramov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
62
packages/redux-devtools-log-monitor/README.md
Normal file
62
packages/redux-devtools-log-monitor/README.md
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
Redux DevTools Log Monitor
|
||||||
|
=========================
|
||||||
|
|
||||||
|
The default monitor for [Redux DevTools](https://github.com/gaearon/redux-devtools) with a tree view.
|
||||||
|
It shows a log of states and actions, and lets you change their history. Created by [Dan Abramov](http://github.com/gaearon) and merged into `redux-devtools` monorepo from [here](https://github.com/gaearon/redux-devtools-log-monitor).
|
||||||
|
|
||||||
|
![](http://i.imgur.com/J4GeW0M.gif)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-dev redux-devtools-log-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
You can use `LogMonitor` as the only monitor in your app:
|
||||||
|
|
||||||
|
##### `containers/DevTools.js`
|
||||||
|
|
||||||
|
```js
|
||||||
|
import React from 'react';
|
||||||
|
import { createDevTools } from 'redux-devtools';
|
||||||
|
import LogMonitor from 'redux-devtools-log-monitor';
|
||||||
|
|
||||||
|
export default createDevTools(
|
||||||
|
<LogMonitor />
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can render `<DevTools>` to any place inside app or even into a separate popup window.
|
||||||
|
|
||||||
|
Alternative, you can use it together with [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor) to make it dockable.
|
||||||
|
Consult the [`DockMonitor` README](https://github.com/gaearon/redux-devtools-dock-monitor) for details of this approach.
|
||||||
|
|
||||||
|
[Read how to start using Redux DevTools.](https://github.com/reduxjs/redux-devtools)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
Every action is displayed in the log. You can expand the tree view to inspect the `action` object and the `state` after it.
|
||||||
|
|
||||||
|
If a reducer throws while handling an action, you will see “Interrupted by an error up the chain” instead of the state and action tree view. Scroll up until you find the action which caused the error. You will see the error message in the action log entry. If you use a hot reloading tool, you can edit the reducer, and the error will automatically update or go away.
|
||||||
|
|
||||||
|
Clicking an action will disable it. It will appear crossed out, and the state will be recalculated as if the action never happened. Clicking it once again will enable it back. Use this together with a hot reloading solution to work sequentially on different states of your app without reproducing them by hand. You can toggle any action except for the initial one.
|
||||||
|
|
||||||
|
There are four buttons at the very top. “Reset” takes your app to the state you created the store with. The other three buttons work together. You might find it useful to think of them like you think of Git commits. “Commit” removes all actions in your log, and makes the current state your initial state. This is useful when you start working on a feature and want to remove the previous noise. After you’ve dispatched a few actions, you can press “Revert” to go back to the last committed state. Finally, if you dispatched some actions by mistake and you don’t want them around, you can toggle them by clicking on them, and press “Sweep” to completely remove all currently disabled actions from the log.
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
Name | Description
|
||||||
|
------------- | -------------
|
||||||
|
`theme` | Either a string referring to one of the themes provided by [redux-devtools-themes](https://github.com/gaearon/redux-devtools-themes) (feel free to contribute!) or a custom object of the same format. Optional. By default, set to [`'nicinabox'`](https://github.com/gaearon/redux-devtools-themes/blob/master/src/nicinabox.js).
|
||||||
|
`select` | A function that selects the slice of the state for DevTools to show. For example, `state => state.thePart.iCare.about`. Optional. By default, set to `state => state`.
|
||||||
|
`preserveScrollTop` | When `true`, records the current scroll top every second so it can be restored on refresh. This only has effect when used together with `persistState()` enhancer from Redux DevTools. By default, set to `true`.
|
||||||
|
`expandActionRoot` | When `true`, displays the action object expanded rather than collapsed. By default, set to `true`.
|
||||||
|
`expandStateRoot` | When `true`, displays the state object expanded rather than collapsed. By default, set to `true`.
|
||||||
|
`markStateDiff` | When `true`, mark the state's values which were changed comparing to the previous state. It affects the performance significantly! You might also want to set `expandStateRoot` to `true` as well when enabling it. By default, set to `false`.
|
||||||
|
`hideMainButtons` | When `true`, will show only the logs without the top button bar. By default, set to `false`.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
MIT
|
66
packages/redux-devtools-log-monitor/package.json
Normal file
66
packages/redux-devtools-log-monitor/package.json
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"name": "redux-devtools-log-monitor",
|
||||||
|
"version": "1.4.0",
|
||||||
|
"description": "The default tree view monitor for Redux DevTools",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"build": "babel src --out-dir lib",
|
||||||
|
"lint": "eslint src test",
|
||||||
|
"test": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive",
|
||||||
|
"test:watch": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive --watch",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"prepublishOnly": "npm run lint && npm run test && npm run clean && npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"redux",
|
||||||
|
"devtools",
|
||||||
|
"flux",
|
||||||
|
"react",
|
||||||
|
"hot reloading",
|
||||||
|
"time travel",
|
||||||
|
"live edit"
|
||||||
|
],
|
||||||
|
"author": "Dan Abramov <dan.abramov@me.com> (http://github.com/gaearon)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.3.15",
|
||||||
|
"babel-core": "^6.1.20",
|
||||||
|
"babel-eslint": "^5.0.0-beta4",
|
||||||
|
"babel-loader": "^6.2.0",
|
||||||
|
"babel-preset-es2015-loose": "^6.1.3",
|
||||||
|
"babel-preset-react": "^6.3.13",
|
||||||
|
"babel-preset-stage-0": "^6.3.13",
|
||||||
|
"eslint": "^0.23",
|
||||||
|
"eslint-config-airbnb": "0.0.6",
|
||||||
|
"eslint-plugin-react": "^3.6.3",
|
||||||
|
"expect": "^1.6.0",
|
||||||
|
"mocha": "^2.2.5",
|
||||||
|
"mocha-jsdom": "^1.0.0",
|
||||||
|
"rimraf": "^2.3.4",
|
||||||
|
"webpack": "^1.11.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^15.0.0 || ^16.0.0",
|
||||||
|
"redux-devtools": "^3.4.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.debounce": "^4.0.4",
|
||||||
|
"prop-types": "^15.0.0",
|
||||||
|
"react-json-tree": "^0.11.0",
|
||||||
|
"react-pure-render": "^1.0.2",
|
||||||
|
"redux-devtools-themes": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
229
packages/redux-devtools-log-monitor/src/LogMonitor.js
Normal file
229
packages/redux-devtools-log-monitor/src/LogMonitor.js
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||||
|
import * as themes from 'redux-devtools-themes';
|
||||||
|
import { ActionCreators } from 'redux-devtools';
|
||||||
|
import { updateScrollTop, startConsecutiveToggle } from './actions';
|
||||||
|
import reducer from './reducers';
|
||||||
|
import LogMonitorButtonBar from './LogMonitorButtonBar';
|
||||||
|
import LogMonitorEntryList from './LogMonitorEntryList';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
|
const { toggleAction, setActionsActive } = ActionCreators;
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: {
|
||||||
|
fontFamily: 'monaco, Consolas, Lucida Console, monospace',
|
||||||
|
position: 'relative',
|
||||||
|
overflowY: 'hidden',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
minWidth: 300,
|
||||||
|
direction: 'ltr'
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
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,
|
||||||
|
consecutiveToggleStartId: PropTypes.number
|
||||||
|
}),
|
||||||
|
|
||||||
|
preserveScrollTop: PropTypes.bool,
|
||||||
|
select: PropTypes.func,
|
||||||
|
theme: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.string
|
||||||
|
]),
|
||||||
|
expandActionRoot: PropTypes.bool,
|
||||||
|
expandStateRoot: PropTypes.bool,
|
||||||
|
markStateDiff: PropTypes.bool,
|
||||||
|
hideMainButtons: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
select: (state) => state,
|
||||||
|
theme: 'nicinabox',
|
||||||
|
preserveScrollTop: true,
|
||||||
|
expandActionRoot: true,
|
||||||
|
expandStateRoot: true,
|
||||||
|
markStateDiff: false
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||||
|
|
||||||
|
updateScrollTop = debounce(() => {
|
||||||
|
const node = this.node;
|
||||||
|
this.props.dispatch(updateScrollTop(node ? node.scrollTop : 0));
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleToggleAction = this.handleToggleAction.bind(this);
|
||||||
|
this.handleToggleConsecutiveAction = this.handleToggleConsecutiveAction.bind(this);
|
||||||
|
this.getRef = this.getRef.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll() {
|
||||||
|
const node = this.node;
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.scrollDown) {
|
||||||
|
const { offsetHeight, scrollHeight } = node;
|
||||||
|
node.scrollTop = scrollHeight - offsetHeight;
|
||||||
|
this.scrollDown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const node = this.node;
|
||||||
|
if (!node || !this.props.monitorState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.preserveScrollTop) {
|
||||||
|
node.scrollTop = this.props.monitorState.initialScrollTop;
|
||||||
|
node.addEventListener('scroll', this.updateScrollTop);
|
||||||
|
} else {
|
||||||
|
this.scrollDown = true;
|
||||||
|
this.scroll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const node = this.node;
|
||||||
|
if (node && this.props.preserveScrollTop) {
|
||||||
|
node.removeEventListener('scroll', this.updateScrollTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const node = this.node;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleAction(id) {
|
||||||
|
this.props.dispatch(toggleAction(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleConsecutiveAction(id) {
|
||||||
|
const { monitorState, actionsById } = this.props;
|
||||||
|
const { consecutiveToggleStartId } = monitorState;
|
||||||
|
if (consecutiveToggleStartId && actionsById[consecutiveToggleStartId]) {
|
||||||
|
const { skippedActionIds } = this.props;
|
||||||
|
const start = Math.min(consecutiveToggleStartId, id);
|
||||||
|
const end = Math.max(consecutiveToggleStartId, id);
|
||||||
|
const active = skippedActionIds.indexOf(consecutiveToggleStartId) > -1;
|
||||||
|
this.props.dispatch(setActionsActive(start, end + 1, active));
|
||||||
|
this.props.dispatch(startConsecutiveToggle(null));
|
||||||
|
} else if (id > 0) {
|
||||||
|
this.props.dispatch(startConsecutiveToggle(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRef(node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const theme = this.getTheme();
|
||||||
|
const { consecutiveToggleStartId } = this.props.monitorState;
|
||||||
|
|
||||||
|
const {
|
||||||
|
dispatch,
|
||||||
|
actionsById,
|
||||||
|
skippedActionIds,
|
||||||
|
stagedActionIds,
|
||||||
|
computedStates,
|
||||||
|
currentStateIndex,
|
||||||
|
select,
|
||||||
|
expandActionRoot,
|
||||||
|
expandStateRoot,
|
||||||
|
markStateDiff
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const entryListProps = {
|
||||||
|
theme,
|
||||||
|
actionsById,
|
||||||
|
skippedActionIds,
|
||||||
|
stagedActionIds,
|
||||||
|
computedStates,
|
||||||
|
currentStateIndex,
|
||||||
|
consecutiveToggleStartId,
|
||||||
|
select,
|
||||||
|
expandActionRoot,
|
||||||
|
expandStateRoot,
|
||||||
|
markStateDiff,
|
||||||
|
onActionClick: this.handleToggleAction,
|
||||||
|
onActionShiftClick: this.handleToggleConsecutiveAction
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{...styles.container, backgroundColor: theme.base00}}>
|
||||||
|
{!this.props.hideMainButtons &&
|
||||||
|
<LogMonitorButtonBar
|
||||||
|
theme={theme}
|
||||||
|
dispatch={dispatch}
|
||||||
|
hasStates={computedStates.length > 1}
|
||||||
|
hasSkippedActions={skippedActionIds.length > 0}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
style={this.props.hideMainButtons ? styles.elements : { ...styles.elements, top: 30 }}
|
||||||
|
ref={this.getRef}
|
||||||
|
>
|
||||||
|
<LogMonitorEntryList {...entryListProps} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
96
packages/redux-devtools-log-monitor/src/LogMonitorButton.js
Normal file
96
packages/redux-devtools-log-monitor/src/LogMonitorButton.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import React from 'react';
|
||||||
|
import brighten from './brighten';
|
||||||
|
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
base: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
borderRadius: 3,
|
||||||
|
padding: 4,
|
||||||
|
marginLeft: 3,
|
||||||
|
marginRight: 3,
|
||||||
|
marginTop: 5,
|
||||||
|
marginBottom: 5,
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'inline-block',
|
||||||
|
fontSize: '0.8em',
|
||||||
|
color: 'white',
|
||||||
|
textDecoration: 'none'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class LogMonitorButton extends React.Component {
|
||||||
|
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||||
|
this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
||||||
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||||
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||||
|
this.onClick = this.onClick.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
hovered: false,
|
||||||
|
active: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter() {
|
||||||
|
this.setState({ hovered: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave() {
|
||||||
|
this.setState({ hovered: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown() {
|
||||||
|
this.setState({ active: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp() {
|
||||||
|
this.setState({ active: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
if (!this.props.enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.props.onClick) {
|
||||||
|
this.props.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let style = {
|
||||||
|
...styles.base,
|
||||||
|
backgroundColor: this.props.theme.base02
|
||||||
|
};
|
||||||
|
if (this.props.enabled && this.state.hovered) {
|
||||||
|
style = {
|
||||||
|
...style,
|
||||||
|
backgroundColor: brighten(this.props.theme.base02, 0.2)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!this.props.enabled) {
|
||||||
|
style = {
|
||||||
|
...style,
|
||||||
|
opacity: 0.2,
|
||||||
|
cursor: 'text',
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onMouseUp={this.handleMouseUp}
|
||||||
|
onClick={this.onClick}
|
||||||
|
style={style}>
|
||||||
|
{this.props.children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||||
|
import { ActionCreators } from 'redux-devtools';
|
||||||
|
import LogMonitorButton from './LogMonitorButton';
|
||||||
|
|
||||||
|
const { reset, rollback, commit, sweep } = ActionCreators;
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
textAlign: 'center',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
borderColor: 'transparent',
|
||||||
|
zIndex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class LogMonitorButtonBar extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
theme: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleReset = this.handleReset.bind(this);
|
||||||
|
this.handleRollback = this.handleRollback.bind(this);
|
||||||
|
this.handleSweep = this.handleSweep.bind(this);
|
||||||
|
this.handleCommit = this.handleCommit.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRollback() {
|
||||||
|
this.props.dispatch(rollback());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSweep() {
|
||||||
|
this.props.dispatch(sweep());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCommit() {
|
||||||
|
this.props.dispatch(commit());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset() {
|
||||||
|
this.props.dispatch(reset());
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { theme, hasStates, hasSkippedActions } = this.props;
|
||||||
|
return (
|
||||||
|
<div style={{...style, borderColor: theme.base02}}>
|
||||||
|
<LogMonitorButton
|
||||||
|
theme={theme}
|
||||||
|
onClick={this.handleReset}
|
||||||
|
enabled>
|
||||||
|
Reset
|
||||||
|
</LogMonitorButton>
|
||||||
|
<LogMonitorButton
|
||||||
|
theme={theme}
|
||||||
|
onClick={this.handleRollback}
|
||||||
|
enabled={hasStates}>
|
||||||
|
Revert
|
||||||
|
</LogMonitorButton>
|
||||||
|
<LogMonitorButton
|
||||||
|
theme={theme}
|
||||||
|
onClick={this.handleSweep}
|
||||||
|
enabled={hasSkippedActions}>
|
||||||
|
Sweep
|
||||||
|
</LogMonitorButton>
|
||||||
|
<LogMonitorButton
|
||||||
|
theme={theme}
|
||||||
|
onClick={this.handleCommit}
|
||||||
|
enabled={hasStates}>
|
||||||
|
Commit
|
||||||
|
</LogMonitorButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
153
packages/redux-devtools-log-monitor/src/LogMonitorEntry.js
Normal file
153
packages/redux-devtools-log-monitor/src/LogMonitorEntry.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import JSONTree from 'react-json-tree';
|
||||||
|
import LogMonitorEntryAction from './LogMonitorEntryAction';
|
||||||
|
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
entry: {
|
||||||
|
display: 'block',
|
||||||
|
WebkitUserSelect: 'none'
|
||||||
|
},
|
||||||
|
|
||||||
|
root: {
|
||||||
|
marginLeft: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDeepItem = (data, path) => path.reduce((obj, key) => obj && obj[key], data);
|
||||||
|
const dataIsEqual = (data, previousData, keyPath) => {
|
||||||
|
const path = [...keyPath].reverse().slice(1);
|
||||||
|
|
||||||
|
return getDeepItem(data, path) === getDeepItem(previousData, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class LogMonitorEntry extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
state: PropTypes.object.isRequired,
|
||||||
|
action: PropTypes.object.isRequired,
|
||||||
|
actionId: PropTypes.number.isRequired,
|
||||||
|
select: PropTypes.func.isRequired,
|
||||||
|
inFuture: PropTypes.bool,
|
||||||
|
error: PropTypes.string,
|
||||||
|
onActionClick: PropTypes.func.isRequired,
|
||||||
|
onActionShiftClick: PropTypes.func.isRequired,
|
||||||
|
collapsed: PropTypes.bool,
|
||||||
|
selected: PropTypes.bool,
|
||||||
|
expandActionRoot: PropTypes.bool,
|
||||||
|
expandStateRoot: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleActionClick = this.handleActionClick.bind(this);
|
||||||
|
this.shouldExpandNode = this.shouldExpandNode.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
printState(state, error) {
|
||||||
|
let errorText = error;
|
||||||
|
if (!errorText) {
|
||||||
|
try {
|
||||||
|
const data = this.props.select(state);
|
||||||
|
let theme;
|
||||||
|
|
||||||
|
if (this.props.markStateDiff) {
|
||||||
|
const previousData = typeof this.props.previousState !== 'undefined' ?
|
||||||
|
this.props.select(this.props.previousState) :
|
||||||
|
undefined;
|
||||||
|
const getValueStyle = ({ style }, nodeType, keyPath) => ({
|
||||||
|
style: {
|
||||||
|
...style,
|
||||||
|
backgroundColor: dataIsEqual(data, previousData, keyPath) ?
|
||||||
|
'transparent' :
|
||||||
|
this.props.theme.base01
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const getNestedNodeStyle = ({ style }, keyPath) => ({
|
||||||
|
style: {
|
||||||
|
...style,
|
||||||
|
...(keyPath.length > 1 ? {} : styles.root)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
theme = {
|
||||||
|
extend: this.props.theme,
|
||||||
|
tree: styles.tree,
|
||||||
|
value: getValueStyle,
|
||||||
|
nestedNode: getNestedNodeStyle
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
theme = this.props.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JSONTree
|
||||||
|
theme={theme}
|
||||||
|
data={data}
|
||||||
|
invertTheme={false}
|
||||||
|
keyPath={['state']}
|
||||||
|
shouldExpandNode={this.shouldExpandNode} />
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
errorText = 'Error selecting state.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
color: this.props.theme.base08,
|
||||||
|
paddingTop: 20,
|
||||||
|
paddingLeft: 30,
|
||||||
|
paddingRight: 30,
|
||||||
|
paddingBottom: 35
|
||||||
|
}}>
|
||||||
|
{errorText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleActionClick(e) {
|
||||||
|
const { actionId, onActionClick, onActionShiftClick } = this.props;
|
||||||
|
if (actionId > 0) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
onActionShiftClick(actionId);
|
||||||
|
} else {
|
||||||
|
onActionClick(actionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExpandNode(keyName, data, level) {
|
||||||
|
return this.props.expandStateRoot && level === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { actionId, error, action, state, collapsed, selected, inFuture } = this.props;
|
||||||
|
const styleEntry = {
|
||||||
|
opacity: collapsed ? 0.5 : 1,
|
||||||
|
cursor: (actionId > 0) ? 'pointer' : 'default'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
opacity: selected ? 0.4 : inFuture ? 0.6 : 1, // eslint-disable-line no-nested-ternary
|
||||||
|
textDecoration: collapsed ? 'line-through' : 'none',
|
||||||
|
color: this.props.theme.base06
|
||||||
|
}}>
|
||||||
|
<LogMonitorEntryAction
|
||||||
|
theme={this.props.theme}
|
||||||
|
collapsed={collapsed}
|
||||||
|
action={action}
|
||||||
|
expandActionRoot={this.props.expandActionRoot}
|
||||||
|
onClick={this.handleActionClick}
|
||||||
|
style={{...styles.entry, ...styleEntry}}/>
|
||||||
|
{!collapsed &&
|
||||||
|
<div style={{ paddingLeft: 16 }}>
|
||||||
|
{this.printState(state, error)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import JSONTree from 'react-json-tree';
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
actionBar: {
|
||||||
|
paddingTop: 8,
|
||||||
|
paddingBottom: 7,
|
||||||
|
paddingLeft: 16
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
margin: 0,
|
||||||
|
paddingLeft: 16,
|
||||||
|
overflow: 'auto'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class LogMonitorAction extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.shouldExpandNode = this.shouldExpandNode.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPayload(payload) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
...styles.payload,
|
||||||
|
backgroundColor: this.props.theme.base00
|
||||||
|
}}>
|
||||||
|
{ Object.keys(payload).length > 0 ?
|
||||||
|
<JSONTree theme={this.props.theme}
|
||||||
|
invertTheme={false}
|
||||||
|
keyPath={['action']}
|
||||||
|
data={payload}
|
||||||
|
shouldExpandNode={this.shouldExpandNode} /> : '' }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExpandNode(keyName, data, level) {
|
||||||
|
return this.props.expandActionRoot && level === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { type, ...payload } = this.props.action;
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: this.props.theme.base02,
|
||||||
|
color: this.props.theme.base06,
|
||||||
|
...this.props.style
|
||||||
|
}}>
|
||||||
|
<div style={styles.actionBar}
|
||||||
|
onClick={this.props.onClick}>
|
||||||
|
{type !== null && type.toString()}
|
||||||
|
</div>
|
||||||
|
{!this.props.collapsed ? this.renderPayload(payload) : ''}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import LogMonitorEntry from './LogMonitorEntry';
|
||||||
|
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||||
|
|
||||||
|
export default class LogMonitorEntryList extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
actionsById: PropTypes.object,
|
||||||
|
computedStates: PropTypes.array,
|
||||||
|
stagedActionIds: PropTypes.array,
|
||||||
|
skippedActionIds: PropTypes.array,
|
||||||
|
currentStateIndex: PropTypes.number,
|
||||||
|
consecutiveToggleStartId: PropTypes.number,
|
||||||
|
|
||||||
|
select: PropTypes.func.isRequired,
|
||||||
|
onActionClick: PropTypes.func.isRequired,
|
||||||
|
theme: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.string
|
||||||
|
]),
|
||||||
|
expandActionRoot: PropTypes.bool,
|
||||||
|
expandStateRoot: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const elements = [];
|
||||||
|
const {
|
||||||
|
theme,
|
||||||
|
actionsById,
|
||||||
|
computedStates,
|
||||||
|
currentStateIndex,
|
||||||
|
consecutiveToggleStartId,
|
||||||
|
select,
|
||||||
|
skippedActionIds,
|
||||||
|
stagedActionIds,
|
||||||
|
expandActionRoot,
|
||||||
|
expandStateRoot,
|
||||||
|
markStateDiff,
|
||||||
|
onActionClick,
|
||||||
|
onActionShiftClick
|
||||||
|
} = 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}
|
||||||
|
inFuture={i > currentStateIndex}
|
||||||
|
selected={consecutiveToggleStartId === i}
|
||||||
|
error={error}
|
||||||
|
expandActionRoot={expandActionRoot}
|
||||||
|
expandStateRoot={expandStateRoot}
|
||||||
|
markStateDiff={markStateDiff}
|
||||||
|
onActionClick={onActionClick}
|
||||||
|
onActionShiftClick={onActionShiftClick} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{elements}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
9
packages/redux-devtools-log-monitor/src/actions.js
Normal file
9
packages/redux-devtools-log-monitor/src/actions.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const UPDATE_SCROLL_TOP = '@@redux-devtools-log-monitor/UPDATE_SCROLL_TOP';
|
||||||
|
export function updateScrollTop(scrollTop) {
|
||||||
|
return { type: UPDATE_SCROLL_TOP, scrollTop };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const START_CONSECUTIVE_TOGGLE = '@@redux-devtools-log-monitor/START_CONSECUTIVE_TOGGLE';
|
||||||
|
export function startConsecutiveToggle(id) {
|
||||||
|
return { type: START_CONSECUTIVE_TOGGLE, id };
|
||||||
|
}
|
16
packages/redux-devtools-log-monitor/src/brighten.js
Normal file
16
packages/redux-devtools-log-monitor/src/brighten.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function(hexColor, lightness) {
|
||||||
|
let hex = String(hexColor).replace(/[^0-9a-f]/gi, '');
|
||||||
|
if (hex.length < 6) {
|
||||||
|
hex = hex.replace(/(.)/g, '$1$1');
|
||||||
|
}
|
||||||
|
let lum = lightness || 0;
|
||||||
|
|
||||||
|
let rgb = '#';
|
||||||
|
let c;
|
||||||
|
for (let i = 0; i < 3; ++i) {
|
||||||
|
c = parseInt(hex.substr(i * 2, 2), 16);
|
||||||
|
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
|
||||||
|
rgb += ('00' + c).substr(c.length);
|
||||||
|
}
|
||||||
|
return rgb;
|
||||||
|
}
|
1
packages/redux-devtools-log-monitor/src/index.js
Normal file
1
packages/redux-devtools-log-monitor/src/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default from './LogMonitor';
|
24
packages/redux-devtools-log-monitor/src/reducers.js
Normal file
24
packages/redux-devtools-log-monitor/src/reducers.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { UPDATE_SCROLL_TOP, START_CONSECUTIVE_TOGGLE } from './actions';
|
||||||
|
|
||||||
|
function initialScrollTop(props, state = 0, action) {
|
||||||
|
if (!props.preserveScrollTop) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return action.type === UPDATE_SCROLL_TOP ?
|
||||||
|
action.scrollTop :
|
||||||
|
state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function startConsecutiveToggle(props, state, action) {
|
||||||
|
return action.type === START_CONSECUTIVE_TOGGLE ?
|
||||||
|
action.id :
|
||||||
|
state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function reducer(props, state = {}, action) {
|
||||||
|
return {
|
||||||
|
initialScrollTop: initialScrollTop(props, state.initialScrollTop, action),
|
||||||
|
consecutiveToggleStartId: startConsecutiveToggle(props, state.consecutiveToggleStartId, action)
|
||||||
|
};
|
||||||
|
}
|
|
@ -7840,7 +7840,7 @@ lodash.debounce@^3.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._getnative "^3.0.0"
|
lodash._getnative "^3.0.0"
|
||||||
|
|
||||||
lodash.debounce@^4.0.3:
|
lodash.debounce@^4.0.3, lodash.debounce@^4.0.4:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||||
|
@ -9618,7 +9618,7 @@ promzard@^0.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
read "1"
|
read "1"
|
||||||
|
|
||||||
prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2:
|
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2:
|
||||||
version "15.6.2"
|
version "15.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||||
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||||
|
|
Loading…
Reference in New Issue
Block a user