mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-03-03 18:58:01 +03:00
Add redux-slider-monitor (#434)
* Add redux-slider-monitor * Fix example configuration of redux-slider-monitor * Fix lint errors * CI: Run build:all before lint
This commit is contained in:
parent
e6fdfb9c9e
commit
476e37a875
|
@ -8,6 +8,6 @@ cache:
|
||||||
directories:
|
directories:
|
||||||
- "node_modules"
|
- "node_modules"
|
||||||
script:
|
script:
|
||||||
- npm run lint
|
|
||||||
- npm run build:all
|
- npm run build:all
|
||||||
|
- npm run lint
|
||||||
- npm test
|
- npm test
|
||||||
|
|
3
packages/redux-slider-monitor/.babelrc
Executable file
3
packages/redux-slider-monitor/.babelrc
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"]
|
||||||
|
}
|
3
packages/redux-slider-monitor/.eslintignore
Executable file
3
packages/redux-slider-monitor/.eslintignore
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
lib
|
||||||
|
**/node_modules
|
||||||
|
examples/**/dist
|
22
packages/redux-slider-monitor/.eslintrc
Executable file
22
packages/redux-slider-monitor/.eslintrc
Executable file
|
@ -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"]
|
||||||
|
}
|
21
packages/redux-slider-monitor/LICENSE.md
Executable file
21
packages/redux-slider-monitor/LICENSE.md
Executable file
|
@ -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.
|
61
packages/redux-slider-monitor/README.md
Executable file
61
packages/redux-slider-monitor/README.md
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
## Redux Slider Monitor
|
||||||
|
|
||||||
|
[](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)
|
||||||
|
|
||||||
|
<image src="https://s3.amazonaws.com/f.cl.ly/items/1I3P222C3N2R1M2y1K3b/Screen%20Recording%202015-12-22%20at%2007.20%20PM.gif?v=1b6267e7" width='800'>
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```npm install redux-slider-monitor```
|
||||||
|
|
||||||
|
### Recommended Usage
|
||||||
|
|
||||||
|
Use with [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor)
|
||||||
|
```javascript
|
||||||
|
<DockMonitor toggleVisibilityKey='ctrl-h'
|
||||||
|
changePositionKey='ctrl-q'
|
||||||
|
defaultPosition='bottom'
|
||||||
|
defaultSize={0.15}>
|
||||||
|
<SliderMonitor keyboardEnabled />
|
||||||
|
</DockMonitor>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
10
packages/redux-slider-monitor/examples/todomvc/.babelrc
Executable file
10
packages/redux-slider-monitor/examples/todomvc/.babelrc
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
["es2015", { "modules": false }],
|
||||||
|
"stage-0",
|
||||||
|
"react"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"react-hot-loader/babel"
|
||||||
|
]
|
||||||
|
}
|
6
packages/redux-slider-monitor/examples/todomvc/README.md
Executable file
6
packages/redux-slider-monitor/examples/todomvc/README.md
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
# Redux DevTools TodoMVC example
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Install dependencies: `npm i`
|
||||||
|
2. Start the development server: `npm start`
|
42
packages/redux-slider-monitor/examples/todomvc/actions/TodoActions.js
Executable file
42
packages/redux-slider-monitor/examples/todomvc/actions/TodoActions.js
Executable file
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
72
packages/redux-slider-monitor/examples/todomvc/components/Footer.js
Executable file
72
packages/redux-slider-monitor/examples/todomvc/components/Footer.js
Executable file
|
@ -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 (
|
||||||
|
<footer className='footer'>
|
||||||
|
{this.renderTodoCount()}
|
||||||
|
<ul className='filters'>
|
||||||
|
{[SHOW_ALL, SHOW_UNMARKED, SHOW_MARKED].map(filter => (
|
||||||
|
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderClearButton()}
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTodoCount() {
|
||||||
|
const { unmarkedCount } = this.props;
|
||||||
|
const itemWord = unmarkedCount === 1 ? 'item' : 'items';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className='todo-count'>
|
||||||
|
<strong>{unmarkedCount || 'No'}</strong> {itemWord} left
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFilterLink(filter) {
|
||||||
|
const title = FILTER_TITLES[filter];
|
||||||
|
const { filter: selectedFilter, onShow } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className={classnames({ selected: filter === selectedFilter })}
|
||||||
|
style={{ cursor: 'hand' }}
|
||||||
|
onClick={() => onShow(filter)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderClearButton() {
|
||||||
|
const { markedCount, onClearMarked } = this.props;
|
||||||
|
if (markedCount > 0) {
|
||||||
|
return (
|
||||||
|
<button className='clear-completed' onClick={onClearMarked}>
|
||||||
|
Clear completed
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
28
packages/redux-slider-monitor/examples/todomvc/components/Header.js
Executable file
28
packages/redux-slider-monitor/examples/todomvc/components/Header.js
Executable file
|
@ -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 (
|
||||||
|
<header className='header'>
|
||||||
|
<h1>todos</h1>
|
||||||
|
<TodoTextInput
|
||||||
|
newTodo
|
||||||
|
onSave={this.handleSave}
|
||||||
|
placeholder='What needs to be done?'
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
packages/redux-slider-monitor/examples/todomvc/components/MainSection.js
Executable file
90
packages/redux-slider-monitor/examples/todomvc/components/MainSection.js
Executable file
|
@ -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 (
|
||||||
|
<section className='main'>
|
||||||
|
{this.renderToggleAll(markedCount)}
|
||||||
|
<ul className='todo-list'>
|
||||||
|
{filteredTodos.map(todo => (
|
||||||
|
<TodoItem key={todo.id} todo={todo} {...actions} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderFooter(markedCount)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToggleAll(markedCount) {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
if (todos.length > 0) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className='toggle-all'
|
||||||
|
type='checkbox'
|
||||||
|
checked={markedCount === todos.length}
|
||||||
|
onChange={actions.markAll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFooter(markedCount) {
|
||||||
|
const { todos } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
const unmarkedCount = todos.length - markedCount;
|
||||||
|
|
||||||
|
if (todos.length) {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
markedCount={markedCount}
|
||||||
|
unmarkedCount={unmarkedCount}
|
||||||
|
filter={filter}
|
||||||
|
onClearMarked={this.handleClearMarked}
|
||||||
|
onShow={this.handleShow}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
77
packages/redux-slider-monitor/examples/todomvc/components/TodoItem.js
Executable file
77
packages/redux-slider-monitor/examples/todomvc/components/TodoItem.js
Executable file
|
@ -0,0 +1,77 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import TodoTextInput from './TodoTextInput';
|
||||||
|
|
||||||
|
export default class TodoItem extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
todo: PropTypes.object.isRequired,
|
||||||
|
editTodo: PropTypes.func.isRequired,
|
||||||
|
deleteTodo: PropTypes.func.isRequired,
|
||||||
|
markTodo: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
editing: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoubleClick = () => {
|
||||||
|
this.setState({ editing: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave = (id, text) => {
|
||||||
|
if (text.length === 0) {
|
||||||
|
this.props.deleteTodo(id);
|
||||||
|
} else {
|
||||||
|
this.props.editTodo(id, text);
|
||||||
|
}
|
||||||
|
this.setState({ editing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todo, markTodo, deleteTodo } = this.props;
|
||||||
|
|
||||||
|
let element;
|
||||||
|
if (this.state.editing) {
|
||||||
|
element = (
|
||||||
|
<TodoTextInput
|
||||||
|
text={todo.text}
|
||||||
|
editing={this.state.editing}
|
||||||
|
onSave={text => this.handleSave(todo.id, text)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element = (
|
||||||
|
<div className='view'>
|
||||||
|
<input
|
||||||
|
className='toggle'
|
||||||
|
type='checkbox'
|
||||||
|
checked={todo.marked}
|
||||||
|
onChange={() => markTodo(todo.id)}
|
||||||
|
/>
|
||||||
|
<label htmlFor='text' onDoubleClick={this.handleDoubleClick}>
|
||||||
|
{todo.text}
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
className='destroy'
|
||||||
|
onClick={() => deleteTodo(todo.id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames({
|
||||||
|
completed: todo.marked,
|
||||||
|
editing: this.state.editing
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
62
packages/redux-slider-monitor/examples/todomvc/components/TodoTextInput.js
Executable file
62
packages/redux-slider-monitor/examples/todomvc/components/TodoTextInput.js
Executable file
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
export default class TodoTextInput extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
onSave: PropTypes.func.isRequired,
|
||||||
|
text: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
newTodo: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
text: '',
|
||||||
|
placeholder: '',
|
||||||
|
editing: false,
|
||||||
|
newTodo: false
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
text: this.props.text || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = (e) => {
|
||||||
|
const text = e.target.value.trim();
|
||||||
|
if (e.which === 13) {
|
||||||
|
this.props.onSave(text);
|
||||||
|
if (this.props.newTodo) {
|
||||||
|
this.setState({ text: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
this.setState({ text: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur = (e) => {
|
||||||
|
if (!this.props.newTodo) {
|
||||||
|
this.props.onSave(e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={classnames({ edit: this.props.editing, 'new-todo': this.props.newTodo })}
|
||||||
|
type='text'
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
autoFocus='true'
|
||||||
|
value={this.state.text}
|
||||||
|
onBlur={this.handleBlur}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={this.handleSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
6
packages/redux-slider-monitor/examples/todomvc/constants/ActionTypes.js
Executable file
6
packages/redux-slider-monitor/examples/todomvc/constants/ActionTypes.js
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
export const ADD_TODO = 'ADD_TODO';
|
||||||
|
export const DELETE_TODO = 'DELETE_TODO';
|
||||||
|
export const EDIT_TODO = 'EDIT_TODO';
|
||||||
|
export const MARK_TODO = 'MARK_TODO';
|
||||||
|
export const MARK_ALL = 'MARK_ALL';
|
||||||
|
export const CLEAR_MARKED = 'CLEAR_MARKED';
|
3
packages/redux-slider-monitor/examples/todomvc/constants/TodoFilters.js
Executable file
3
packages/redux-slider-monitor/examples/todomvc/constants/TodoFilters.js
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
export const SHOW_ALL = 'show_all';
|
||||||
|
export const SHOW_MARKED = 'show_marked';
|
||||||
|
export const SHOW_UNMARKED = 'show_unmarked';
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { createDevTools } from 'redux-devtools';
|
||||||
|
import DockMonitor from 'redux-devtools-dock-monitor';
|
||||||
|
import SliderMonitor from 'redux-slider-monitor'; // eslint-disable-line
|
||||||
|
|
||||||
|
export default createDevTools(
|
||||||
|
<DockMonitor
|
||||||
|
toggleVisibilityKey='ctrl-h'
|
||||||
|
changePositionKey='ctrl-q'
|
||||||
|
defaultPosition='bottom'
|
||||||
|
defaultSize={0.15}
|
||||||
|
>
|
||||||
|
<SliderMonitor keyboardEnabled />
|
||||||
|
</DockMonitor>
|
||||||
|
);
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import TodoApp from './TodoApp';
|
||||||
|
import DevTools from './DevTools';
|
||||||
|
|
||||||
|
const Root = ({ store }) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<div>
|
||||||
|
<TodoApp />
|
||||||
|
<DevTools />
|
||||||
|
</div>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
Root.propTypes = {
|
||||||
|
store: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Root;
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./Root.prod');
|
||||||
|
} else {
|
||||||
|
module.exports = require('./Root.dev');
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import TodoApp from './TodoApp';
|
||||||
|
|
||||||
|
const Root = ({ store }) => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<div>
|
||||||
|
<TodoApp />
|
||||||
|
</div>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
Root.propTypes = {
|
||||||
|
store: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Root;
|
33
packages/redux-slider-monitor/examples/todomvc/containers/TodoApp.js
Executable file
33
packages/redux-slider-monitor/examples/todomvc/containers/TodoApp.js
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import Header from '../components/Header';
|
||||||
|
import MainSection from '../components/MainSection';
|
||||||
|
import * as TodoActions from '../actions/TodoActions';
|
||||||
|
|
||||||
|
const TodoApp = ({ todos, actions }) => (
|
||||||
|
<div>
|
||||||
|
<Header addTodo={actions.addTodo} />
|
||||||
|
<MainSection todos={todos} actions={actions} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
TodoApp.propTypes = {
|
||||||
|
todos: PropTypes.array.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapState(state) {
|
||||||
|
return {
|
||||||
|
todos: state.todos
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatch(dispatch) {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(TodoActions, dispatch)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapState, mapDispatch)(TodoApp);
|
23
packages/redux-slider-monitor/examples/todomvc/index.js
Executable file
23
packages/redux-slider-monitor/examples/todomvc/index.js
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'todomvc-app-css/index.css';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { AppContainer } from 'react-hot-loader';
|
||||||
|
import configureStore from './store/configureStore';
|
||||||
|
import Root from './containers/Root';
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
const rootEl = document.getElementById('root');
|
||||||
|
const render = () => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppContainer>
|
||||||
|
<Root store={store} />
|
||||||
|
</AppContainer>,
|
||||||
|
rootEl
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(Root);
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./containers/Root', render);
|
||||||
|
}
|
15
packages/redux-slider-monitor/examples/todomvc/package.json
Normal file
15
packages/redux-slider-monitor/examples/todomvc/package.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "todomvc",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "TodoMVC example for redux",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "../../node_modules/.bin/webpack-dev-server",
|
||||||
|
"build": "../../node_modules/.bin/webpack --config webpack.config.prod.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/calesce/redux-slider-monitor.git"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
8
packages/redux-slider-monitor/examples/todomvc/reducers/index.js
Executable file
8
packages/redux-slider-monitor/examples/todomvc/reducers/index.js
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import todos from './todos';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
50
packages/redux-slider-monitor/examples/todomvc/reducers/todos.js
Executable file
50
packages/redux-slider-monitor/examples/todomvc/reducers/todos.js
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED } from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
const initialState = [{
|
||||||
|
text: 'Use Redux',
|
||||||
|
marked: false,
|
||||||
|
id: 0
|
||||||
|
}];
|
||||||
|
|
||||||
|
export default function todos(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_TODO:
|
||||||
|
return [{
|
||||||
|
id: (state.length === 0) ? 0 : state[0].id + 1,
|
||||||
|
marked: false,
|
||||||
|
text: action.text
|
||||||
|
}, ...state];
|
||||||
|
|
||||||
|
case DELETE_TODO:
|
||||||
|
return state.filter(todo =>
|
||||||
|
todo.id !== action.id
|
||||||
|
);
|
||||||
|
|
||||||
|
case EDIT_TODO:
|
||||||
|
return state.map(todo => (
|
||||||
|
todo.id === action.id ?
|
||||||
|
{ ...todo, text: action.text } :
|
||||||
|
todo
|
||||||
|
));
|
||||||
|
|
||||||
|
case MARK_TODO:
|
||||||
|
return state.map(todo => (
|
||||||
|
todo.id === action.id ?
|
||||||
|
{ ...todo, marked: !todo.marked } :
|
||||||
|
todo
|
||||||
|
));
|
||||||
|
|
||||||
|
case MARK_ALL: {
|
||||||
|
const areAllMarked = state.every(todo => todo.marked);
|
||||||
|
return state.map(todo => ({
|
||||||
|
...todo,
|
||||||
|
marked: !areAllMarked
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
case CLEAR_MARKED:
|
||||||
|
return state.filter(todo => todo.marked === false);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { createStore, compose } from 'redux';
|
||||||
|
import { persistState } from 'redux-devtools';
|
||||||
|
import rootReducer from '../reducers';
|
||||||
|
import DevTools from '../containers/DevTools';
|
||||||
|
|
||||||
|
const finalCreateStore = compose(
|
||||||
|
DevTools.instrument(),
|
||||||
|
persistState(
|
||||||
|
window.location.href.match(
|
||||||
|
/[?&]debug_session=([^&]+)\b/
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)(createStore);
|
||||||
|
|
||||||
|
export default function configureStore(initialState) {
|
||||||
|
const store = finalCreateStore(rootReducer, initialState);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('../reducers', () => store.replaceReducer(rootReducer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
module.exports = require('./configureStore.prod');
|
||||||
|
} else {
|
||||||
|
module.exports = require('./configureStore.dev');
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { createStore } from 'redux';
|
||||||
|
import rootReducer from '../reducers';
|
||||||
|
|
||||||
|
export default function configureStore(initialState) {
|
||||||
|
return createStore(rootReducer, initialState);
|
||||||
|
}
|
48
packages/redux-slider-monitor/examples/todomvc/webpack.config.js
Executable file
48
packages/redux-slider-monitor/examples/todomvc/webpack.config.js
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devtool: 'eval-cheap-module-source-map',
|
||||||
|
devServer: {
|
||||||
|
contentBase: path.join(__dirname, 'dist'),
|
||||||
|
host: 'localhost',
|
||||||
|
port: process.env.PORT || 3000,
|
||||||
|
historyApiFallback: true,
|
||||||
|
hot: true
|
||||||
|
},
|
||||||
|
entry: [
|
||||||
|
'react-hot-loader/patch',
|
||||||
|
'webpack-dev-server/client?http://localhost:3000',
|
||||||
|
'webpack/hot/only-dev-server',
|
||||||
|
'./index'
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js'
|
||||||
|
},
|
||||||
|
plugins: [new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'redux-slider-monitor': path.join(__dirname, '..', '..', 'src/SliderMonitor')
|
||||||
|
},
|
||||||
|
extensions: ['.js']
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
include: [__dirname, path.join(__dirname, '../../src')]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css?$/,
|
||||||
|
use: ['style-loader', 'raw-loader'],
|
||||||
|
include: [
|
||||||
|
__dirname,
|
||||||
|
path.join(__dirname, '../../../../node_modules/todomvc-app-css')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
// NOTE: This config is used for deploy to gh-pages
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const devConfig = require('./webpack.config');
|
||||||
|
|
||||||
|
devConfig.entry = './index';
|
||||||
|
devConfig.plugins = [
|
||||||
|
new webpack.NoEmitOnErrorsPlugin()
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = devConfig;
|
60
packages/redux-slider-monitor/package.json
Normal file
60
packages/redux-slider-monitor/package.json
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
{
|
||||||
|
"name": "redux-slider-monitor",
|
||||||
|
"version": "2.0.0-2",
|
||||||
|
"description": "A custom monitor for replaying Redux actions that works similarly to a video player",
|
||||||
|
"main": "lib/SliderMonitor.js",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"build": "babel src --out-dir lib",
|
||||||
|
"lint": "eslint src examples",
|
||||||
|
"prepublish": "npm run lint && npm run clean && npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools"
|
||||||
|
},
|
||||||
|
"author": "Cale Newman <newman.cale@gmail.com> (http://github.com/calesce)",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.24.1",
|
||||||
|
"babel-core": "^6.24.1",
|
||||||
|
"babel-eslint": "^7.2.2",
|
||||||
|
"babel-loader": "^6.4.1",
|
||||||
|
"babel-preset-es2015": "^6.24.1",
|
||||||
|
"babel-preset-react": "^6.24.1",
|
||||||
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
|
"classnames": "^2.1.2",
|
||||||
|
"eslint": "^5.0.0",
|
||||||
|
"eslint-config-airbnb": "^14.1.0",
|
||||||
|
"eslint-plugin-import": "^2.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^4.0.0",
|
||||||
|
"eslint-plugin-react": "^7.4.0",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
|
"react": "^16.7.0",
|
||||||
|
"react-dom": "^16.7.0",
|
||||||
|
"react-redux": "^6.0.0",
|
||||||
|
"react-hot-loader": "^3.0.0-beta.6",
|
||||||
|
"redux": "^4.0.0",
|
||||||
|
"redux-devtools-dock-monitor": "^1.0.1",
|
||||||
|
"redux-devtools-log-monitor": "^1.0.1",
|
||||||
|
"redux-devtools": "^3.5.0",
|
||||||
|
"rimraf": "^2.3.4",
|
||||||
|
"style-loader": "^0.16.1",
|
||||||
|
"todomvc-app-css": "^2.0.1",
|
||||||
|
"webpack": "^2.2.1",
|
||||||
|
"webpack-dev-server": "^2.4.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^0.14.0 || ^15.0.0 || ^16.0.0-0",
|
||||||
|
"react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0-0",
|
||||||
|
"redux-devtools": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"devui": "^1.0.0-3",
|
||||||
|
"prop-types": "^15.5.8",
|
||||||
|
"redux-devtools-themes": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
93
packages/redux-slider-monitor/src/SliderButton.js
Normal file
93
packages/redux-slider-monitor/src/SliderButton.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import React, { Component, PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Button from 'devui/lib/Button';
|
||||||
|
|
||||||
|
export default class SliderButton extends (PureComponent || Component) {
|
||||||
|
static propTypes = {
|
||||||
|
theme: PropTypes.object,
|
||||||
|
type: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
iconStyle() {
|
||||||
|
return {
|
||||||
|
cursor: 'hand',
|
||||||
|
fill: this.props.theme.base06,
|
||||||
|
width: '1.8rem',
|
||||||
|
height: '1.8rem'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPlayButton() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
title='Play'
|
||||||
|
size='small'
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
theme={this.props.theme}
|
||||||
|
>
|
||||||
|
<svg viewBox='0 0 24 24' preserveAspectRatio='xMidYMid meet' style={this.iconStyle()}>
|
||||||
|
<g>
|
||||||
|
<path d='M8 5v14l11-7z' />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPauseButton = () => (
|
||||||
|
<Button
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
title='Pause'
|
||||||
|
size='small'
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
theme={this.props.theme}
|
||||||
|
>
|
||||||
|
<svg viewBox='0 0 24 24' preserveAspectRatio='xMidYMid meet' style={this.iconStyle()}>
|
||||||
|
<g>
|
||||||
|
<path d='M6 19h4V5H6v14zm8-14v14h4V5h-4z' />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
renderStepButton = (direction) => {
|
||||||
|
const isLeft = direction === 'left';
|
||||||
|
const d = isLeft
|
||||||
|
? 'M15.41 16.09l-4.58-4.59 4.58-4.59-1.41-1.41-6 6 6 6z'
|
||||||
|
: 'M8.59 16.34l4.58-4.59-4.58-4.59 1.41-1.41 6 6-6 6z';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
title={isLeft ? 'Go back' : 'Go forward'}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
theme={this.props.theme}
|
||||||
|
>
|
||||||
|
<svg viewBox='0 0 24 24' preserveAspectRatio='xMidYMid meet' style={this.iconStyle()}>
|
||||||
|
<g>
|
||||||
|
<path d={d} />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
switch (this.props.type) {
|
||||||
|
case 'play':
|
||||||
|
return this.renderPlayButton();
|
||||||
|
case 'pause':
|
||||||
|
return this.renderPauseButton();
|
||||||
|
case 'stepLeft':
|
||||||
|
return this.renderStepButton('left');
|
||||||
|
case 'stepRight':
|
||||||
|
return this.renderStepButton('right');
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
311
packages/redux-slider-monitor/src/SliderMonitor.js
Normal file
311
packages/redux-slider-monitor/src/SliderMonitor.js
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
import React, { Component, PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import * as themes from 'redux-devtools-themes';
|
||||||
|
import { ActionCreators } from 'redux-devtools';
|
||||||
|
import { Toolbar, Divider } from 'devui/lib/Toolbar';
|
||||||
|
import Slider from 'devui/lib/Slider';
|
||||||
|
import Button from 'devui/lib/Button';
|
||||||
|
import SegmentedControl from 'devui/lib/SegmentedControl';
|
||||||
|
|
||||||
|
import reducer from './reducers';
|
||||||
|
import SliderButton from './SliderButton';
|
||||||
|
|
||||||
|
const { reset, jumpToState } = ActionCreators;
|
||||||
|
|
||||||
|
export default class SliderMonitor extends (PureComponent || Component) {
|
||||||
|
static update = reducer;
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
computedStates: PropTypes.array,
|
||||||
|
stagedActionIds: PropTypes.array,
|
||||||
|
actionsById: PropTypes.object,
|
||||||
|
currentStateIndex: PropTypes.number,
|
||||||
|
monitorState: PropTypes.shape({
|
||||||
|
initialScrollTop: PropTypes.number
|
||||||
|
}),
|
||||||
|
preserveScrollTop: PropTypes.bool,
|
||||||
|
stagedActions: PropTypes.array,
|
||||||
|
select: PropTypes.func.isRequired,
|
||||||
|
hideResetButton: PropTypes.bool,
|
||||||
|
theme: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.string
|
||||||
|
]),
|
||||||
|
keyboardEnabled: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
select: state => state,
|
||||||
|
theme: 'nicinabox',
|
||||||
|
preserveScrollTop: true,
|
||||||
|
keyboardEnabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
timer: undefined,
|
||||||
|
replaySpeed: '1x'
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('keydown', this.handleKeyPress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.removeEventListener('keydown', this.handleKeyPress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpTheme = () => {
|
||||||
|
let theme;
|
||||||
|
if (typeof this.props.theme === 'string') {
|
||||||
|
if (typeof themes[this.props.theme] !== 'undefined') {
|
||||||
|
theme = themes[this.props.theme];
|
||||||
|
} else {
|
||||||
|
theme = themes.nicinabox;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
theme = this.props.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset = () => {
|
||||||
|
this.pauseReplay();
|
||||||
|
this.props.dispatch(reset());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyPress = (event) => {
|
||||||
|
if (!this.props.keyboardEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (event.ctrlKey && event.keyCode === 74) { // ctrl+j
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (this.state.timer) {
|
||||||
|
return this.pauseReplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.replaySpeed === 'Live') {
|
||||||
|
this.startRealtimeReplay();
|
||||||
|
} else {
|
||||||
|
this.startReplay();
|
||||||
|
}
|
||||||
|
} else if (event.ctrlKey && event.keyCode === 219) { // ctrl+[
|
||||||
|
event.preventDefault();
|
||||||
|
this.stepLeft();
|
||||||
|
} else if (event.ctrlKey && event.keyCode === 221) { // ctrl+]
|
||||||
|
event.preventDefault();
|
||||||
|
this.stepRight();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSliderChange = (value) => {
|
||||||
|
if (this.state.timer) {
|
||||||
|
this.pauseReplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.dispatch(jumpToState(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
startReplay = () => {
|
||||||
|
const { computedStates, currentStateIndex, dispatch } = this.props;
|
||||||
|
|
||||||
|
if (computedStates.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const speed = this.state.replaySpeed === '1x' ? 500 : 200;
|
||||||
|
|
||||||
|
let stateIndex;
|
||||||
|
if (currentStateIndex === computedStates.length - 1) {
|
||||||
|
dispatch(jumpToState(0));
|
||||||
|
stateIndex = 0;
|
||||||
|
} else if (currentStateIndex === computedStates.length - 2) {
|
||||||
|
dispatch(jumpToState(currentStateIndex + 1));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
stateIndex = currentStateIndex + 1;
|
||||||
|
dispatch(jumpToState(currentStateIndex + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let counter = stateIndex;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (counter + 1 <= computedStates.length - 1) {
|
||||||
|
dispatch(jumpToState(counter + 1));
|
||||||
|
}
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
if (counter >= computedStates.length - 1) {
|
||||||
|
clearInterval(this.state.timer);
|
||||||
|
this.setState({
|
||||||
|
timer: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, speed);
|
||||||
|
|
||||||
|
this.setState({ timer });
|
||||||
|
}
|
||||||
|
|
||||||
|
startRealtimeReplay = () => {
|
||||||
|
if (this.props.computedStates.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.currentStateIndex === this.props.computedStates.length - 1) {
|
||||||
|
this.props.dispatch(jumpToState(0));
|
||||||
|
|
||||||
|
this.loop(0);
|
||||||
|
} else {
|
||||||
|
this.loop(this.props.currentStateIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop = (index) => {
|
||||||
|
let currentTimestamp = Date.now();
|
||||||
|
let timestampDiff = this.getLatestTimestampDiff(index);
|
||||||
|
|
||||||
|
const aLoop = () => {
|
||||||
|
const replayDiff = Date.now() - currentTimestamp;
|
||||||
|
if (replayDiff >= timestampDiff) {
|
||||||
|
this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));
|
||||||
|
|
||||||
|
if (this.props.currentStateIndex >= this.props.computedStates.length - 1) {
|
||||||
|
this.pauseReplay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timestampDiff = this.getLatestTimestampDiff(this.props.currentStateIndex);
|
||||||
|
currentTimestamp = Date.now();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timer: requestAnimationFrame(aLoop)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
timer: requestAnimationFrame(aLoop)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (index !== this.props.computedStates.length - 1) {
|
||||||
|
this.setState({
|
||||||
|
timer: requestAnimationFrame(aLoop)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestTimestampDiff = index =>
|
||||||
|
this.getTimestampOfStateIndex(index + 1) - this.getTimestampOfStateIndex(index)
|
||||||
|
|
||||||
|
getTimestampOfStateIndex = (stateIndex) => {
|
||||||
|
const id = this.props.stagedActionIds[stateIndex];
|
||||||
|
return this.props.actionsById[id].timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseReplay = (cb) => {
|
||||||
|
if (this.state.timer) {
|
||||||
|
cancelAnimationFrame(this.state.timer);
|
||||||
|
clearInterval(this.state.timer);
|
||||||
|
this.setState({
|
||||||
|
timer: undefined
|
||||||
|
}, () => {
|
||||||
|
if (typeof cb === 'function') {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stepLeft = () => {
|
||||||
|
this.pauseReplay();
|
||||||
|
|
||||||
|
if (this.props.currentStateIndex !== 0) {
|
||||||
|
this.props.dispatch(jumpToState(this.props.currentStateIndex - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stepRight = () => {
|
||||||
|
this.pauseReplay();
|
||||||
|
|
||||||
|
if (this.props.currentStateIndex !== this.props.computedStates.length - 1) {
|
||||||
|
this.props.dispatch(jumpToState(this.props.currentStateIndex + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeReplaySpeed = (replaySpeed) => {
|
||||||
|
this.setState({ replaySpeed });
|
||||||
|
|
||||||
|
if (this.state.timer) {
|
||||||
|
this.pauseReplay(() => {
|
||||||
|
if (replaySpeed === 'Live') {
|
||||||
|
this.startRealtimeReplay();
|
||||||
|
} else {
|
||||||
|
this.startReplay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
currentStateIndex, computedStates, actionsById, stagedActionIds, hideResetButton
|
||||||
|
} = this.props;
|
||||||
|
const { replaySpeed } = this.state;
|
||||||
|
const theme = this.setUpTheme();
|
||||||
|
|
||||||
|
const max = computedStates.length - 1;
|
||||||
|
const actionId = stagedActionIds[currentStateIndex];
|
||||||
|
let actionType = actionsById[actionId].action.type;
|
||||||
|
if (actionType === undefined) actionType = '<UNDEFINED>';
|
||||||
|
else if (actionType === null) actionType = '<NULL>';
|
||||||
|
else actionType = actionType.toString() || '<EMPTY>';
|
||||||
|
|
||||||
|
const onPlayClick = replaySpeed === 'Live' ? this.startRealtimeReplay : this.startReplay;
|
||||||
|
const playPause = this.state.timer ?
|
||||||
|
<SliderButton theme={theme} type='pause' onClick={this.pauseReplay} /> :
|
||||||
|
<SliderButton theme={theme} type='play' disabled={max <= 0} onClick={onPlayClick} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toolbar noBorder compact fullHeight theme={theme}>
|
||||||
|
{playPause}
|
||||||
|
<Slider
|
||||||
|
label={actionType}
|
||||||
|
sublabel={`(${actionId})`}
|
||||||
|
min={0}
|
||||||
|
max={max}
|
||||||
|
value={currentStateIndex}
|
||||||
|
onChange={this.handleSliderChange}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<SliderButton
|
||||||
|
theme={theme}
|
||||||
|
type='stepLeft'
|
||||||
|
disabled={currentStateIndex <= 0}
|
||||||
|
onClick={this.stepLeft}
|
||||||
|
/>
|
||||||
|
<SliderButton
|
||||||
|
theme={theme}
|
||||||
|
type='stepRight'
|
||||||
|
disabled={currentStateIndex === max}
|
||||||
|
onClick={this.stepRight}
|
||||||
|
/>
|
||||||
|
<Divider theme={theme} />
|
||||||
|
<SegmentedControl
|
||||||
|
theme={theme}
|
||||||
|
values={['Live', '1x', '2x']}
|
||||||
|
selected={replaySpeed}
|
||||||
|
onClick={this.changeReplaySpeed}
|
||||||
|
/>
|
||||||
|
{!hideResetButton && [
|
||||||
|
<Divider key='divider' theme={theme} />,
|
||||||
|
<Button key='reset' theme={theme} onClick={this.handleReset}>Reset</Button>
|
||||||
|
]}
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
packages/redux-slider-monitor/src/reducers.js
Normal file
3
packages/redux-slider-monitor/src/reducers.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function reducer() {
|
||||||
|
return {};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user