mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-03-03 10:45:48 +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:
|
||||
- "node_modules"
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run build:all
|
||||
- npm run lint
|
||||
- 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