From e6fdfb9c9ec48f3c5ab6598a8dd0953af58b7309 Mon Sep 17 00:00:00 2001 From: Mihail Diordiev Date: Sat, 22 Dec 2018 02:50:57 +0200 Subject: [PATCH] Merge redux-devtools-log-monitor (#432) * Move from gaearon/redux-devtools-log-monitor * Npm package config and add credits --- .../redux-devtools-inspector/package.json | 2 + packages/redux-devtools-log-monitor/.babelrc | 3 + .../redux-devtools-log-monitor/.eslintignore | 2 + packages/redux-devtools-log-monitor/.eslintrc | 20 ++ .../redux-devtools-log-monitor/LICENSE.md | 21 ++ packages/redux-devtools-log-monitor/README.md | 62 +++++ .../redux-devtools-log-monitor/package.json | 66 +++++ .../src/LogMonitor.js | 229 ++++++++++++++++++ .../src/LogMonitorButton.js | 96 ++++++++ .../src/LogMonitorButtonBar.js | 82 +++++++ .../src/LogMonitorEntry.js | 153 ++++++++++++ .../src/LogMonitorEntryAction.js | 59 +++++ .../src/LogMonitorEntryList.js | 79 ++++++ .../redux-devtools-log-monitor/src/actions.js | 9 + .../src/brighten.js | 16 ++ .../redux-devtools-log-monitor/src/index.js | 1 + .../src/reducers.js | 24 ++ .../test/index.spec.js | 0 yarn.lock | 4 +- 19 files changed, 926 insertions(+), 2 deletions(-) create mode 100644 packages/redux-devtools-log-monitor/.babelrc create mode 100644 packages/redux-devtools-log-monitor/.eslintignore create mode 100644 packages/redux-devtools-log-monitor/.eslintrc create mode 100644 packages/redux-devtools-log-monitor/LICENSE.md create mode 100644 packages/redux-devtools-log-monitor/README.md create mode 100644 packages/redux-devtools-log-monitor/package.json create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitor.js create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitorButton.js create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitorButtonBar.js create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitorEntry.js create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitorEntryAction.js create mode 100644 packages/redux-devtools-log-monitor/src/LogMonitorEntryList.js create mode 100644 packages/redux-devtools-log-monitor/src/actions.js create mode 100644 packages/redux-devtools-log-monitor/src/brighten.js create mode 100644 packages/redux-devtools-log-monitor/src/index.js create mode 100644 packages/redux-devtools-log-monitor/src/reducers.js create mode 100644 packages/redux-devtools-log-monitor/test/index.spec.js diff --git a/packages/redux-devtools-inspector/package.json b/packages/redux-devtools-inspector/package.json index fff06e9d..2ef20b5d 100644 --- a/packages/redux-devtools-inspector/package.json +++ b/packages/redux-devtools-inspector/package.json @@ -12,6 +12,8 @@ "version": "npm run build:demo && git add -A .", "postversion": "git push", "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" }, "main": "lib/index.js", diff --git a/packages/redux-devtools-log-monitor/.babelrc b/packages/redux-devtools-log-monitor/.babelrc new file mode 100644 index 00000000..65836a67 --- /dev/null +++ b/packages/redux-devtools-log-monitor/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015-loose", "stage-0", "react"] +} diff --git a/packages/redux-devtools-log-monitor/.eslintignore b/packages/redux-devtools-log-monitor/.eslintignore new file mode 100644 index 00000000..81b0eb72 --- /dev/null +++ b/packages/redux-devtools-log-monitor/.eslintignore @@ -0,0 +1,2 @@ +lib +**/node_modules diff --git a/packages/redux-devtools-log-monitor/.eslintrc b/packages/redux-devtools-log-monitor/.eslintrc new file mode 100644 index 00000000..47dc0576 --- /dev/null +++ b/packages/redux-devtools-log-monitor/.eslintrc @@ -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" + ] +} diff --git a/packages/redux-devtools-log-monitor/LICENSE.md b/packages/redux-devtools-log-monitor/LICENSE.md new file mode 100644 index 00000000..af2353dc --- /dev/null +++ b/packages/redux-devtools-log-monitor/LICENSE.md @@ -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. diff --git a/packages/redux-devtools-log-monitor/README.md b/packages/redux-devtools-log-monitor/README.md new file mode 100644 index 00000000..d672727e --- /dev/null +++ b/packages/redux-devtools-log-monitor/README.md @@ -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( + +); +``` + +Then you can render `` 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 diff --git a/packages/redux-devtools-log-monitor/package.json b/packages/redux-devtools-log-monitor/package.json new file mode 100644 index 00000000..c790da84 --- /dev/null +++ b/packages/redux-devtools-log-monitor/package.json @@ -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 (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" + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitor.js b/packages/redux-devtools-log-monitor/src/LogMonitor.js new file mode 100644 index 00000000..a2130458 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitor.js @@ -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 ( +
+ {!this.props.hideMainButtons && + 1} + hasSkippedActions={skippedActionIds.length > 0} + /> + } +
+ +
+
+ ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitorButton.js b/packages/redux-devtools-log-monitor/src/LogMonitorButton.js new file mode 100644 index 00000000..eab3a439 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitorButton.js @@ -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 ( + + {this.props.children} + + ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitorButtonBar.js b/packages/redux-devtools-log-monitor/src/LogMonitorButtonBar.js new file mode 100644 index 00000000..48b50b04 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitorButtonBar.js @@ -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 ( +
+ + Reset + + + Revert + + + Sweep + + + Commit + +
+ ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitorEntry.js b/packages/redux-devtools-log-monitor/src/LogMonitorEntry.js new file mode 100644 index 00000000..971afb81 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitorEntry.js @@ -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 ( + + ); + } catch (err) { + errorText = 'Error selecting state.'; + } + } + + return ( +
+ {errorText} +
+ ); + } + + 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 ( +
+ + {!collapsed && +
+ {this.printState(state, error)} +
+ } +
+ ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitorEntryAction.js b/packages/redux-devtools-log-monitor/src/LogMonitorEntryAction.js new file mode 100644 index 00000000..dfebc8a4 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitorEntryAction.js @@ -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 ( +
+ { Object.keys(payload).length > 0 ? + : '' } +
+ ); + } + + shouldExpandNode(keyName, data, level) { + return this.props.expandActionRoot && level === 0; + } + + render() { + const { type, ...payload } = this.props.action; + return ( +
+
+ {type !== null && type.toString()} +
+ {!this.props.collapsed ? this.renderPayload(payload) : ''} +
+ ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/LogMonitorEntryList.js b/packages/redux-devtools-log-monitor/src/LogMonitorEntryList.js new file mode 100644 index 00000000..2a74044a --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/LogMonitorEntryList.js @@ -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( + -1} + inFuture={i > currentStateIndex} + selected={consecutiveToggleStartId === i} + error={error} + expandActionRoot={expandActionRoot} + expandStateRoot={expandStateRoot} + markStateDiff={markStateDiff} + onActionClick={onActionClick} + onActionShiftClick={onActionShiftClick} /> + ); + } + + return ( +
+ {elements} +
+ ); + } +} diff --git a/packages/redux-devtools-log-monitor/src/actions.js b/packages/redux-devtools-log-monitor/src/actions.js new file mode 100644 index 00000000..b46bd7be --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/actions.js @@ -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 }; +} diff --git a/packages/redux-devtools-log-monitor/src/brighten.js b/packages/redux-devtools-log-monitor/src/brighten.js new file mode 100644 index 00000000..8599ec1d --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/brighten.js @@ -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; +} diff --git a/packages/redux-devtools-log-monitor/src/index.js b/packages/redux-devtools-log-monitor/src/index.js new file mode 100644 index 00000000..0a3a289b --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/index.js @@ -0,0 +1 @@ +export default from './LogMonitor'; diff --git a/packages/redux-devtools-log-monitor/src/reducers.js b/packages/redux-devtools-log-monitor/src/reducers.js new file mode 100644 index 00000000..60198378 --- /dev/null +++ b/packages/redux-devtools-log-monitor/src/reducers.js @@ -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) + }; +} diff --git a/packages/redux-devtools-log-monitor/test/index.spec.js b/packages/redux-devtools-log-monitor/test/index.spec.js new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock index aed1cad2..a0da2164 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7840,7 +7840,7 @@ lodash.debounce@^3.1.1: dependencies: lodash._getnative "^3.0.0" -lodash.debounce@^4.0.3: +lodash.debounce@^4.0.3, lodash.debounce@^4.0.4: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= @@ -9618,7 +9618,7 @@ promzard@^0.3.0: dependencies: 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" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==