Merge redux-devtools-log-monitor (#432)

* Move from gaearon/redux-devtools-log-monitor

* Npm package config and add credits
This commit is contained in:
Mihail Diordiev 2018-12-22 02:50:57 +02:00 committed by GitHub
parent 89880265a6
commit e6fdfb9c9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 926 additions and 2 deletions

View File

@ -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",

View File

@ -0,0 +1,3 @@
{
"presets": ["es2015-loose", "stage-0", "react"]
}

View File

@ -0,0 +1,2 @@
lib
**/node_modules

View 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"
]
}

View 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.

View 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 youve 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 dont 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

View 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"
}
}

View 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>
);
}
}

View 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>
);
}
}

View File

@ -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>
);
}
}

View 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>
);
}
}

View File

@ -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>
);
}
}

View File

@ -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>
);
}
}

View 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 };
}

View 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;
}

View File

@ -0,0 +1 @@
export default from './LogMonitor';

View 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)
};
}

View File

@ -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==