diff --git a/.travis.yml b/.travis.yml index b9114a18e6..781e050847 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ cache: directories: - "node_modules" script: - - npm run lint - npm run build:all + - npm run lint - npm test diff --git a/packages/redux-slider-monitor/.babelrc b/packages/redux-slider-monitor/.babelrc new file mode 100755 index 0000000000..9b7d435ad3 --- /dev/null +++ b/packages/redux-slider-monitor/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015", "stage-0", "react"] +} diff --git a/packages/redux-slider-monitor/.eslintignore b/packages/redux-slider-monitor/.eslintignore new file mode 100755 index 0000000000..07e20aaacd --- /dev/null +++ b/packages/redux-slider-monitor/.eslintignore @@ -0,0 +1,3 @@ +lib +**/node_modules +examples/**/dist diff --git a/packages/redux-slider-monitor/.eslintrc b/packages/redux-slider-monitor/.eslintrc new file mode 100755 index 0000000000..c874d2e4e6 --- /dev/null +++ b/packages/redux-slider-monitor/.eslintrc @@ -0,0 +1,22 @@ +{ + "extends": "eslint-config-airbnb", + "env": { + "browser": true, + "mocha": true, + "node": true + }, + "parser": "babel-eslint", + "rules": { + "comma-dangle": [2, "never"], + "jsx-quotes": [2, "prefer-single"], + "react/jsx-uses-react": 2, + "react/jsx-uses-vars": 2, + "react/react-in-jsx-scope": 2, + "react/sort-comp": 0, + "react/forbid-prop-types": 0, + "import/no-extraneous-dependencies": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "jsx-a11y/no-static-element-interactions": 0 + }, + "plugins": ["react"] +} diff --git a/packages/redux-slider-monitor/LICENSE.md b/packages/redux-slider-monitor/LICENSE.md new file mode 100755 index 0000000000..52bc2726ff --- /dev/null +++ b/packages/redux-slider-monitor/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cale Newman + +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-slider-monitor/README.md b/packages/redux-slider-monitor/README.md new file mode 100755 index 0000000000..86820b10cf --- /dev/null +++ b/packages/redux-slider-monitor/README.md @@ -0,0 +1,61 @@ +## Redux Slider Monitor + +[![npm version](https://img.shields.io/npm/v/redux-slider-monitor.svg?style=flat-square)](https://www.npmjs.com/package/redux-slider-monitor) + +A custom monitor for use with [Redux DevTools](https://github.com/gaearon/redux-devtools). + +It uses a slider based on [react-slider](https://github.com/mpowaga/react-slider) to slide between different recorded actions. It also features play/pause/step-through, which is inspired by some very cool [Elm](http://elm-lang.org/) [examples](http://elm-lang.org/blog/time-travel-made-easy). + +[Try out the demo!](https://calesce.github.io/redux-slider-monitor/?debug_session=123) + + + +### Installation + +```npm install redux-slider-monitor``` + +### Recommended Usage + +Use with [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor) +```javascript + + + +``` + +Dispatch some Redux actions. Use the slider to navigate between the state changes. + +Click the play/pause buttons to watch the state changes over time, or step backward or forward in state time with the left/right arrow buttons. Change replay speeds with the ```1x``` button, and "Live" will replay actions with the same time intervals in which they originally were dispatched. + +## Keyboard shortcuts + +Pass the ```keyboardEnabled``` prop to use these shortcuts + +```ctrl+j```: play/pause + +```ctrl+[```: step backward + +```ctrl+]```: step forward + + +### Running Examples + +You can do this: + +``` +git clone https://github.com/calesce/redux-slider-monitor.git +cd redux-slider-monitor +npm install + +cd examples/todomvc +npm install +npm start +open http://localhost:3000 +``` + +### License + +MIT diff --git a/packages/redux-slider-monitor/examples/todomvc/.babelrc b/packages/redux-slider-monitor/examples/todomvc/.babelrc new file mode 100755 index 0000000000..b772750d8e --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": [ + ["es2015", { "modules": false }], + "stage-0", + "react" + ], + "plugins": [ + "react-hot-loader/babel" + ] +} diff --git a/packages/redux-slider-monitor/examples/todomvc/README.md b/packages/redux-slider-monitor/examples/todomvc/README.md new file mode 100755 index 0000000000..da2a8208eb --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/README.md @@ -0,0 +1,6 @@ +# Redux DevTools TodoMVC example + +## Getting Started + +1. Install dependencies: `npm i` +2. Start the development server: `npm start` diff --git a/packages/redux-slider-monitor/examples/todomvc/actions/TodoActions.js b/packages/redux-slider-monitor/examples/todomvc/actions/TodoActions.js new file mode 100755 index 0000000000..f8cfb289f3 --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/actions/TodoActions.js @@ -0,0 +1,42 @@ +import * as types from '../constants/ActionTypes'; + +export function addTodo(text) { + return { + type: types.ADD_TODO, + text + }; +} + +export function deleteTodo(id) { + return { + type: types.DELETE_TODO, + id + }; +} + +export function editTodo(id, text) { + return { + type: types.EDIT_TODO, + id, + text + }; +} + +export function markTodo(id) { + return { + type: types.MARK_TODO, + id + }; +} + +export function markAll() { + return { + type: types.MARK_ALL + }; +} + +export function clearMarked() { + return { + type: types.CLEAR_MARKED + }; +} diff --git a/packages/redux-slider-monitor/examples/todomvc/components/Footer.js b/packages/redux-slider-monitor/examples/todomvc/components/Footer.js new file mode 100755 index 0000000000..9b83337120 --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/components/Footer.js @@ -0,0 +1,72 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters'; + +const FILTER_TITLES = { + [SHOW_ALL]: 'All', + [SHOW_UNMARKED]: 'Active', + [SHOW_MARKED]: 'Completed' +}; + +export default class Footer extends Component { + static propTypes = { + markedCount: PropTypes.number.isRequired, + unmarkedCount: PropTypes.number.isRequired, + filter: PropTypes.string.isRequired, + onClearMarked: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired + }; + + render() { + return ( + + ); + } + + renderTodoCount() { + const { unmarkedCount } = this.props; + const itemWord = unmarkedCount === 1 ? 'item' : 'items'; + + return ( + + {unmarkedCount || 'No'} {itemWord} left + + ); + } + + renderFilterLink(filter) { + const title = FILTER_TITLES[filter]; + const { filter: selectedFilter, onShow } = this.props; + + return ( + onShow(filter)} + > + {title} + + ); + } + + renderClearButton() { + const { markedCount, onClearMarked } = this.props; + if (markedCount > 0) { + return ( + + ); + } + return null; + } +} diff --git a/packages/redux-slider-monitor/examples/todomvc/components/Header.js b/packages/redux-slider-monitor/examples/todomvc/components/Header.js new file mode 100755 index 0000000000..698a0f70ba --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/components/Header.js @@ -0,0 +1,28 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import TodoTextInput from './TodoTextInput'; + +export default class Header extends Component { + static propTypes = { + addTodo: PropTypes.func.isRequired + }; + + handleSave = (text) => { + if (text.length !== 0) { + this.props.addTodo(text); + } + } + + render() { + return ( +
+

todos

+ +
+ ); + } +} diff --git a/packages/redux-slider-monitor/examples/todomvc/components/MainSection.js b/packages/redux-slider-monitor/examples/todomvc/components/MainSection.js new file mode 100755 index 0000000000..f42dec46cd --- /dev/null +++ b/packages/redux-slider-monitor/examples/todomvc/components/MainSection.js @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import TodoItem from './TodoItem'; +import Footer from './Footer'; +import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters'; + +const TODO_FILTERS = { + [SHOW_ALL]: () => true, + [SHOW_UNMARKED]: todo => !todo.marked, + [SHOW_MARKED]: todo => todo.marked +}; + +export default class MainSection extends Component { + static propTypes = { + todos: PropTypes.array.isRequired, + actions: PropTypes.object.isRequired + }; + + constructor(props, context) { + super(props, context); + this.handleClearMarked = this.handleClearMarked.bind(this); + this.handleShow = this.handleShow.bind(this); + this.state = { filter: SHOW_ALL }; + } + + handleClearMarked() { + const atLeastOneMarked = this.props.todos.some(todo => todo.marked); + if (atLeastOneMarked) { + this.props.actions.clearMarked(); + } + } + + handleShow(filter) { + this.setState({ filter }); + } + + render() { + const { todos, actions } = this.props; + const { filter } = this.state; + + const filteredTodos = todos.filter(TODO_FILTERS[filter]); + const markedCount = todos.reduce((count, todo) => (todo.marked ? count + 1 : count), 0); + + return ( +
+ {this.renderToggleAll(markedCount)} + + {this.renderFooter(markedCount)} +
+ ); + } + + renderToggleAll(markedCount) { + const { todos, actions } = this.props; + if (todos.length > 0) { + return ( + + ); + } + return null; + } + + renderFooter(markedCount) { + const { todos } = this.props; + const { filter } = this.state; + const unmarkedCount = todos.length - markedCount; + + if (todos.length) { + return ( +