diff --git a/packages/redux-devtools-chart-monitor/package.json b/packages/redux-devtools-chart-monitor/package.json index 4adcb87b..4e654eda 100644 --- a/packages/redux-devtools-chart-monitor/package.json +++ b/packages/redux-devtools-chart-monitor/package.json @@ -7,7 +7,7 @@ "clean": "rimraf lib", "build": "babel src --out-dir lib", "prepare": "npm run build", - "prepublishOnly": "npm run lint && npm run clean && npm run build" + "prepublishOnly": "npm run clean && npm run build" }, "files": [ "lib", diff --git a/packages/redux-devtools-dock-monitor/.babelrc b/packages/redux-devtools-dock-monitor/.babelrc new file mode 100644 index 00000000..645cc56e --- /dev/null +++ b/packages/redux-devtools-dock-monitor/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"], + "plugins": ["@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-export-default-from"] +} diff --git a/packages/redux-devtools-dock-monitor/README.md b/packages/redux-devtools-dock-monitor/README.md new file mode 100644 index 00000000..ffeac664 --- /dev/null +++ b/packages/redux-devtools-dock-monitor/README.md @@ -0,0 +1,63 @@ +# Redux DevTools Dock Monitor + +A resizable and movable dock for [Redux DevTools](https://github.com/reduxjs/redux-devtools). +Powered by [React Dock](https://github.com/alexkuz/react-dock). + +![](http://i.imgur.com/QbNzNW4.gif) + +### Installation + +``` +yarn add redux-devtools-dock-monitor +``` + +### Usage + +Wrap any other Redux DevTools monitor in `DockMonitor` to make it dockable to different screen edges. +For example, you can use it together with [`LogMonitor`](https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-log-monitor): + +##### `containers/DevTools.js` + +```js +import React from 'react'; +import { createDevTools } from 'redux-devtools'; +import LogMonitor from 'redux-devtools-log-monitor'; +import SliderMonitor from 'redux-slider-monitor'; +import DockMonitor from 'redux-devtools-dock-monitor'; + +export default createDevTools( + + + + +); +``` + +[Read how to start using Redux DevTools.](https://github.com/reduxjs/redux-devtools) + +#### Multiple Monitors + +You can put more than one monitor inside ``. There will still be a single dock, but you will be able to switch between different monitors by pressing a key specified as `changeMonitorKey` prop. + +### Props + +| Name | Description | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `children` | Any valid Redux DevTools monitor. Required. | +| `toggleVisibilityKey` | A key or a key combination that toggles the dock visibility. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-h'`). Required. | +| `changePositionKey` | A key or a key combination that toggles the dock position. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-w'`). Required. | +| `changeMonitorKey` | A key or a key combination that switches the currently visible monitor. Must be recognizable by [parse-key](https://github.com/thlorenz/parse-key) (for example, `'ctrl-m'`). Required if you use more than one monitor. | +| `fluid` | When `true`, the dock size is a fraction of the window size, fixed otherwise. Optional. By default set to `true`. | +| `defaultSize` | Size of the dock. When `fluid` is `true`, a float (`0.5` means half the window size). When `fluid` is `false`, a width in pixels. Optional. By default set to `0.3` (3/10th of the window size). | +| `defaultPosition` | Where the dock appears on the screen. Valid values: `'left'`, `'top'`, `'right'`, `'bottom'`. Optional. By default set to `'right'`. | +| `defaultIsVisible` | Defines whether dock should be open by default. A value of `true` means that it's open when the page/app loads. | + +The current size and the position are persisted between sessions with `persistState()` enhancer from Redux DevTools. + +### License + +MIT diff --git a/packages/redux-devtools-dock-monitor/package.json b/packages/redux-devtools-dock-monitor/package.json new file mode 100644 index 00000000..fcf2d4ee --- /dev/null +++ b/packages/redux-devtools-dock-monitor/package.json @@ -0,0 +1,56 @@ +{ + "name": "redux-devtools-dock-monitor", + "version": "1.1.3", + "description": "A resizable and movable dock for Redux DevTools monitors", + "main": "lib/index.js", + "files": [ + "lib", + "src" + ], + "scripts": { + "clean": "rimraf lib", + "build": "babel src --out-dir lib", + "prepare": "npm run build", + "prepublishOnly": "npm run test && npm run clean && npm run build" + }, + "repository": { + "type": "git", + "url": "https://github.com/reduxjs/redux-devtools.git" + }, + "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": "^7.10.5", + "@babel/core": "^7.11.0", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-export-default-from": "^7.10.4", + "@babel/preset-env": "^7.11.0", + "@babel/preset-react": "^7.10.4", + "babel-loader": "^8.1.0", + "rimraf": "^2.7.1" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0", + "redux-devtools": "^3.4.0" + }, + "dependencies": { + "babel-runtime": "^6.26.0", + "parse-key": "^0.2.1", + "prop-types": "^15.7.2", + "react-dock": "^0.2.4", + "react-pure-render": "^1.0.2" + } +} diff --git a/packages/redux-devtools-dock-monitor/src/DockMonitor.js b/packages/redux-devtools-dock-monitor/src/DockMonitor.js new file mode 100644 index 00000000..572715d4 --- /dev/null +++ b/packages/redux-devtools-dock-monitor/src/DockMonitor.js @@ -0,0 +1,162 @@ +import React, { cloneElement, Children, Component } from 'react'; +import PropTypes from 'prop-types'; +import Dock from 'react-dock'; +import { POSITIONS } from './constants'; +import { + toggleVisibility, + changeMonitor, + changePosition, + changeSize +} from './actions'; +import reducer from './reducers'; +import parseKey from 'parse-key'; + +export default class DockMonitor extends Component { + static update = reducer; + + static propTypes = { + defaultPosition: PropTypes.oneOf(POSITIONS), + defaultIsVisible: PropTypes.bool.isRequired, + defaultSize: PropTypes.number.isRequired, + toggleVisibilityKey: PropTypes.string.isRequired, + changePositionKey: PropTypes.string.isRequired, + changeMonitorKey: PropTypes.string, + fluid: PropTypes.bool, + + dispatch: PropTypes.func, + monitorState: PropTypes.shape({ + position: PropTypes.oneOf(POSITIONS).isRequired, + size: PropTypes.number.isRequired, + isVisible: PropTypes.bool.isRequired, + childMonitorState: PropTypes.any + }) + }; + + static defaultProps = { + defaultIsVisible: true, + defaultPosition: 'right', + defaultSize: 0.3, + fluid: true + }; + + constructor(props) { + super(props); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleSizeChange = this.handleSizeChange.bind(this); + + const childrenCount = Children.count(props.children); + if (childrenCount === 0) { + // eslint-disable-next-line no-console + console.error( + ' requires at least one monitor inside. ' + + 'Why don’t you try ? You can get it at ' + + 'https://github.com/gaearon/redux-devtools-log-monitor.' + ); + } else if (childrenCount > 1 && !props.changeMonitorKey) { + // eslint-disable-next-line no-console + console.error( + 'You specified multiple monitors inside ' + + 'but did not provide `changeMonitorKey` prop to change them. ' + + 'Try specifying ' + + 'and then press Ctrl-M.' + ); + } + } + + componentDidMount() { + window.addEventListener('keydown', this.handleKeyDown); + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.handleKeyDown); + } + + matchesKey(key, event) { + if (!key) { + return false; + } + + const charCode = event.keyCode || event.which; + const char = String.fromCharCode(charCode); + return ( + key.name.toUpperCase() === char.toUpperCase() && + key.alt === event.altKey && + key.ctrl === event.ctrlKey && + key.meta === event.metaKey && + key.shift === event.shiftKey + ); + } + + handleKeyDown(e) { + // Ignore regular keys when focused on a field + // and no modifiers are active. + if ( + !e.ctrlKey && + !e.metaKey && + !e.altKey && + (e.target.tagName === 'INPUT' || + e.target.tagName === 'SELECT' || + e.target.tagName === 'TEXTAREA' || + e.target.isContentEditable) + ) { + return; + } + + const visibilityKey = parseKey(this.props.toggleVisibilityKey); + const positionKey = parseKey(this.props.changePositionKey); + + let monitorKey; + if (this.props.changeMonitorKey) { + monitorKey = parseKey(this.props.changeMonitorKey); + } + + if (this.matchesKey(visibilityKey, e)) { + e.preventDefault(); + this.props.dispatch(toggleVisibility()); + } else if (this.matchesKey(positionKey, e)) { + e.preventDefault(); + this.props.dispatch(changePosition()); + } else if (this.matchesKey(monitorKey, e)) { + e.preventDefault(); + this.props.dispatch(changeMonitor()); + } + } + + handleSizeChange(requestedSize) { + this.props.dispatch(changeSize(requestedSize)); + } + + renderChild(child, index, otherProps) { + const { monitorState } = this.props; + const { childMonitorIndex, childMonitorStates } = monitorState; + + if (index !== childMonitorIndex) { + return null; + } + + return cloneElement(child, { + monitorState: childMonitorStates[index], + ...otherProps + }); + } + + render() { + const { monitorState, children, fluid, ...rest } = this.props; + const { position, isVisible, size } = monitorState; + + return ( + + {Children.map(children, (child, index) => + this.renderChild(child, index, rest) + )} + + ); + } +} diff --git a/packages/redux-devtools-dock-monitor/src/actions.js b/packages/redux-devtools-dock-monitor/src/actions.js new file mode 100644 index 00000000..d253293c --- /dev/null +++ b/packages/redux-devtools-dock-monitor/src/actions.js @@ -0,0 +1,20 @@ +export const TOGGLE_VISIBILITY = + '@@redux-devtools-log-monitor/TOGGLE_VISIBILITY'; +export function toggleVisibility() { + return { type: TOGGLE_VISIBILITY }; +} + +export const CHANGE_POSITION = '@@redux-devtools-log-monitor/CHANGE_POSITION'; +export function changePosition() { + return { type: CHANGE_POSITION }; +} + +export const CHANGE_SIZE = '@@redux-devtools-log-monitor/CHANGE_SIZE'; +export function changeSize(size) { + return { type: CHANGE_SIZE, size: size }; +} + +export const CHANGE_MONITOR = '@@redux-devtools-log-monitor/CHANGE_MONITOR'; +export function changeMonitor() { + return { type: CHANGE_MONITOR }; +} diff --git a/packages/redux-devtools-dock-monitor/src/constants.js b/packages/redux-devtools-dock-monitor/src/constants.js new file mode 100644 index 00000000..e935427f --- /dev/null +++ b/packages/redux-devtools-dock-monitor/src/constants.js @@ -0,0 +1 @@ +export const POSITIONS = ['left', 'top', 'right', 'bottom']; diff --git a/packages/redux-devtools-dock-monitor/src/index.js b/packages/redux-devtools-dock-monitor/src/index.js new file mode 100644 index 00000000..5b553f1d --- /dev/null +++ b/packages/redux-devtools-dock-monitor/src/index.js @@ -0,0 +1 @@ +export default from './DockMonitor'; diff --git a/packages/redux-devtools-dock-monitor/src/reducers.js b/packages/redux-devtools-dock-monitor/src/reducers.js new file mode 100644 index 00000000..12bf5d4d --- /dev/null +++ b/packages/redux-devtools-dock-monitor/src/reducers.js @@ -0,0 +1,68 @@ +import { + CHANGE_MONITOR, + CHANGE_POSITION, + CHANGE_SIZE, + TOGGLE_VISIBILITY +} from './actions'; +import { POSITIONS } from './constants'; +import { Children } from 'react'; + +function position(props, state = props.defaultPosition, action) { + return action.type === CHANGE_POSITION + ? POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] + : state; +} + +function size(props, state = props.defaultSize, action) { + return action.type === CHANGE_SIZE ? action.size : state; +} + +function isVisible(props, state = props.defaultIsVisible, action) { + return action.type === TOGGLE_VISIBILITY ? !state : state; +} + +function childMonitorStates(props, state = [], action) { + return Children.map(props.children, (child, index) => + child.type.update(child.props, state[index], action) + ); +} + +function childMonitorIndex(props, state = 0, action) { + switch (action.type) { + case CHANGE_MONITOR: + return (state + 1) % Children.count(props.children); + default: + return state; + } +} + +export default function reducer(props, state = {}, action) { + if (!state.childMonitorStates) { + Children.forEach(props.children, (child, index) => { + if (typeof child.type.update !== 'function') { + // eslint-disable-next-line no-console + console.error( + `Child of with the index ${index} ` + + `(${child.type.displayName || child.type.name || child.type}) ` + + 'does not appear to be a valid Redux DevTools monitor.' + ); + } + }); + } + + return { + position: position(props, state.position, action), + isVisible: isVisible(props, state.isVisible, action), + size: size(props, state.size, action), + childMonitorIndex: childMonitorIndex( + props, + state.childMonitorIndex, + action + ), + childMonitorStates: childMonitorStates( + props, + state.childMonitorStates, + action + ) + }; +}