mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-07-27 08:30:02 +03:00
prettier
This commit is contained in:
parent
c6870a7f7b
commit
452380aa00
|
@ -26,7 +26,5 @@
|
||||||
"new-cap": [2, { "capIsNewExceptions": ["Test"] }],
|
"new-cap": [2, { "capIsNewExceptions": ["Test"] }],
|
||||||
"default-case": 0
|
"default-case": 0
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["react"]
|
||||||
"react"
|
|
||||||
]
|
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "6"
|
- '6'
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- $HOME/.yarn-cache
|
- $HOME/.yarn-cache
|
||||||
|
@ -19,7 +19,7 @@ addons:
|
||||||
- g++-4.8
|
- g++-4.8
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
|
- '/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16'
|
||||||
- npm install -g yarn
|
- npm install -g yarn
|
||||||
- yarn install
|
- yarn install
|
||||||
|
|
||||||
|
|
|
@ -14,21 +14,21 @@ appearance, race, religion, or sexual identity and orientation.
|
||||||
Examples of behavior that contributes to creating a positive environment
|
Examples of behavior that contributes to creating a positive environment
|
||||||
include:
|
include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
- Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
- Being respectful of differing viewpoints and experiences
|
||||||
* Gracefully accepting constructive criticism
|
- Gracefully accepting constructive criticism
|
||||||
* Focusing on what is best for the community
|
- Focusing on what is best for the community
|
||||||
* Showing empathy towards other community members
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
advances
|
advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
- Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic
|
- Publishing others' private information, such as a physical or electronic
|
||||||
address, without explicit permission
|
address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
- Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
|
@ -10,19 +10,23 @@
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. For Chrome
|
### 1. For Chrome
|
||||||
|
|
||||||
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
|
- from [Chrome Web Store](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd);
|
||||||
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
|
- or download `extension.zip` from [last releases](https://github.com/zalmoxisus/redux-devtools-extension/releases), unzip, open `chrome://extensions` url and turn on developer mode from top left and then click; on `Load Unpacked` and select the extracted folder for use
|
||||||
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./build/extension`;
|
- or build it with `npm i && npm run build:extension` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./build/extension`;
|
||||||
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./dev`.
|
- or run it in dev mode with `npm i && npm start` and [load the extension's folder](https://developer.chrome.com/extensions/getstarted#unpacked) `./dev`.
|
||||||
|
|
||||||
### 2. For Firefox
|
### 2. For Firefox
|
||||||
|
|
||||||
- from [Mozilla Add-ons](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/);
|
- from [Mozilla Add-ons](https://addons.mozilla.org/en-US/firefox/addon/reduxdevtools/);
|
||||||
- or build it with `npm i && npm run build:firefox` and [load the extension's folder](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox) `./build/firefox` (just select a file from inside the dir).
|
- or build it with `npm i && npm run build:firefox` and [load the extension's folder](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox) `./build/firefox` (just select a file from inside the dir).
|
||||||
|
|
||||||
### 3. For Electron
|
### 3. For Electron
|
||||||
|
|
||||||
- just specify `REDUX_DEVTOOLS` in [`electron-devtools-installer`](https://github.com/GPMDP/electron-devtools-installer).
|
- just specify `REDUX_DEVTOOLS` in [`electron-devtools-installer`](https://github.com/GPMDP/electron-devtools-installer).
|
||||||
|
|
||||||
### 4. For other browsers and non-browser environment
|
### 4. For other browsers and non-browser environment
|
||||||
|
|
||||||
- use [`remote-redux-devtools`](https://github.com/zalmoxisus/remote-redux-devtools).
|
- use [`remote-redux-devtools`](https://github.com/zalmoxisus/remote-redux-devtools).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -30,9 +34,11 @@
|
||||||
> Note that starting from v2.7, `window.devToolsExtension` was renamed to `window.__REDUX_DEVTOOLS_EXTENSION__` / `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`.
|
> Note that starting from v2.7, `window.devToolsExtension` was renamed to `window.__REDUX_DEVTOOLS_EXTENSION__` / `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`.
|
||||||
|
|
||||||
## 1. With Redux
|
## 1. With Redux
|
||||||
|
|
||||||
### 1.1 Basic store
|
### 1.1 Basic store
|
||||||
|
|
||||||
For a basic [Redux store](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) simply add:
|
For a basic [Redux store](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) simply add:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducer, /* preloadedState, */
|
reducer, /* preloadedState, */
|
||||||
|
@ -43,16 +49,22 @@ For a basic [Redux store](https://redux.js.org/api/createstore#createstorereduce
|
||||||
Note that [`preloadedState`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) argument is optional in Redux's [`createStore`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer).
|
Note that [`preloadedState`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer) argument is optional in Redux's [`createStore`](https://redux.js.org/api/createstore#createstorereducer-preloadedstate-enhancer).
|
||||||
|
|
||||||
> For universal ("isomorphic") apps, prefix it with `typeof window !== 'undefined' &&`.
|
> For universal ("isomorphic") apps, prefix it with `typeof window !== 'undefined' &&`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const composeEnhancers = (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
|
const composeEnhancers =
|
||||||
|
(typeof window !== 'undefined' &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
|
||||||
|
compose;
|
||||||
```
|
```
|
||||||
|
|
||||||
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtools-extension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
|
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtools-extension-package-from-npm), which contains all the definitions, or just use `(window as any)` (see [Recipes](/docs/Recipes.md#using-in-a-typescript-project) for an example).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
```
|
```
|
||||||
|
|
||||||
In case ESLint is configured to not allow using the underscore dangle, wrap it like so:
|
In case ESLint is configured to not allow using the underscore dangle, wrap it like so:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
+ /* eslint-disable no-underscore-dangle */
|
+ /* eslint-disable no-underscore-dangle */
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
|
@ -67,7 +79,9 @@ In case ESLint is configured to not allow using the underscore dangle, wrap it l
|
||||||
> You don't need to npm install [`redux-devtools`](https://github.com/gaearon/redux-devtools) when using the extension (that's a different lib).
|
> You don't need to npm install [`redux-devtools`](https://github.com/gaearon/redux-devtools) when using the extension (that's a different lib).
|
||||||
|
|
||||||
### 1.2 Advanced store setup
|
### 1.2 Advanced store setup
|
||||||
|
|
||||||
If you setup your store with [middleware and enhancers](http://redux.js.org/docs/api/applyMiddleware.html), change:
|
If you setup your store with [middleware and enhancers](http://redux.js.org/docs/api/applyMiddleware.html), change:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
|
||||||
|
@ -77,19 +91,21 @@ If you setup your store with [middleware and enhancers](http://redux.js.org/docs
|
||||||
applyMiddleware(...middleware)
|
applyMiddleware(...middleware)
|
||||||
));
|
));
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note that when the extension is not installed, we’re using Redux compose here.
|
> Note that when the extension is not installed, we’re using Redux compose here.
|
||||||
|
|
||||||
To specify [extension’s options](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md), use it like so:
|
To specify [extension’s options](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md), use it like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const composeEnhancers =
|
const composeEnhancers =
|
||||||
typeof window === 'object' &&
|
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
|
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
|
||||||
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
|
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
|
||||||
}) : compose;
|
})
|
||||||
|
: compose;
|
||||||
|
|
||||||
const enhancer = composeEnhancers(
|
const enhancer = composeEnhancers(
|
||||||
applyMiddleware(...middleware),
|
applyMiddleware(...middleware)
|
||||||
// other store enhancers if any
|
// other store enhancers if any
|
||||||
);
|
);
|
||||||
const store = createStore(reducer, enhancer);
|
const store = createStore(reducer, enhancer);
|
||||||
|
@ -100,20 +116,28 @@ const store = createStore(reducer, enhancer);
|
||||||
### 1.3 Use `redux-devtools-extension` package from npm
|
### 1.3 Use `redux-devtools-extension` package from npm
|
||||||
|
|
||||||
To make things easier, there's an npm package to install:
|
To make things easier, there's an npm package to install:
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install --save redux-devtools-extension
|
npm install --save redux-devtools-extension
|
||||||
```
|
```
|
||||||
|
|
||||||
and to use like so:
|
and to use like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
|
|
||||||
const store = createStore(reducer, composeWithDevTools(
|
const store = createStore(
|
||||||
applyMiddleware(...middleware),
|
reducer,
|
||||||
|
composeWithDevTools(
|
||||||
|
applyMiddleware(...middleware)
|
||||||
// other store enhancers if any
|
// other store enhancers if any
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
To specify [extension’s options](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#windowdevtoolsextensionconfig):
|
To specify [extension’s options](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#windowdevtoolsextensionconfig):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
|
@ -121,36 +145,49 @@ import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
const composeEnhancers = composeWithDevTools({
|
const composeEnhancers = composeWithDevTools({
|
||||||
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
|
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
|
||||||
});
|
});
|
||||||
const store = createStore(reducer, /* preloadedState, */ composeEnhancers(
|
const store = createStore(
|
||||||
applyMiddleware(...middleware),
|
reducer,
|
||||||
|
/* preloadedState, */ composeEnhancers(
|
||||||
|
applyMiddleware(...middleware)
|
||||||
// other store enhancers if any
|
// other store enhancers if any
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
> There’re just [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
|
> There’re just [few lines of code](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/index.js) added to your bundle.
|
||||||
|
|
||||||
In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`:
|
In case you don't include other enhancers and middlewares, just use `devToolsEnhancer`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createStore } from 'redux';
|
import { createStore } from 'redux';
|
||||||
import { devToolsEnhancer } from 'redux-devtools-extension';
|
import { devToolsEnhancer } from 'redux-devtools-extension';
|
||||||
|
|
||||||
const store = createStore(reducer, /* preloadedState, */ devToolsEnhancer(
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
/* preloadedState, */ devToolsEnhancer()
|
||||||
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
|
// Specify name here, actionsBlacklist, actionsCreators and other options if needed
|
||||||
));
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.4 Using in production
|
### 1.4 Using in production
|
||||||
|
|
||||||
It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
|
It's useful to include the extension in production as well. Usually you [can use it for development](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
|
||||||
|
|
||||||
If you want to restrict it there, use `redux-devtools-extension/logOnlyInProduction`:
|
If you want to restrict it there, use `redux-devtools-extension/logOnlyInProduction`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createStore } from 'redux';
|
import { createStore } from 'redux';
|
||||||
import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction';
|
import { devToolsEnhancer } from 'redux-devtools-extension/logOnlyInProduction';
|
||||||
|
|
||||||
const store = createStore(reducer, /* preloadedState, */ devToolsEnhancer(
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
/* preloadedState, */ devToolsEnhancer()
|
||||||
// options like actionSanitizer, stateSanitizer
|
// options like actionSanitizer, stateSanitizer
|
||||||
));
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
or with middlewares and enhancers:
|
or with middlewares and enhancers:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||||
|
@ -158,11 +195,15 @@ or with middlewares and enhancers:
|
||||||
const composeEnhancers = composeWithDevTools({
|
const composeEnhancers = composeWithDevTools({
|
||||||
// options like actionSanitizer, stateSanitizer
|
// options like actionSanitizer, stateSanitizer
|
||||||
});
|
});
|
||||||
const store = createStore(reducer, /* preloadedState, */ composeEnhancers(
|
const store = createStore(
|
||||||
applyMiddleware(...middleware),
|
reducer,
|
||||||
|
/* preloadedState, */ composeEnhancers(
|
||||||
|
applyMiddleware(...middleware)
|
||||||
// other store enhancers if any
|
// other store enhancers if any
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
> You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257)
|
> You'll have to add `'process.env.NODE_ENV': JSON.stringify('production')` in your Webpack config for the production bundle ([to envify](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md#exclude-devtools-from-production-builds)). If you use `create-react-app`, [it already does it for you.](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpack.config.prod.js#L253-L257)
|
||||||
|
|
||||||
If you're already checking `process.env.NODE_ENV` when creating the store, include `redux-devtools-extension/logOnly` for production environment.
|
If you're already checking `process.env.NODE_ENV` when creating the store, include `redux-devtools-extension/logOnly` for production environment.
|
||||||
|
@ -172,14 +213,17 @@ or with middlewares and enhancers:
|
||||||
> See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details.
|
> See [the article](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f) for more details.
|
||||||
|
|
||||||
### 1.5 For React Native, hybrid, desktop and server side Redux apps
|
### 1.5 For React Native, hybrid, desktop and server side Redux apps
|
||||||
|
|
||||||
For React Native we can use [`react-native-debugger`](https://github.com/jhen0409/react-native-debugger), which already included [the same API](https://github.com/jhen0409/react-native-debugger/blob/master/docs/redux-devtools-integration.md) with Redux DevTools Extension.
|
For React Native we can use [`react-native-debugger`](https://github.com/jhen0409/react-native-debugger), which already included [the same API](https://github.com/jhen0409/react-native-debugger/blob/master/docs/redux-devtools-integration.md) with Redux DevTools Extension.
|
||||||
|
|
||||||
For most platforms, include [`Remote Redux DevTools`](https://github.com/zalmoxisus/remote-redux-devtools)'s store enhancer, and from the extension's context menu choose 'Open Remote DevTools' for remote monitoring.
|
For most platforms, include [`Remote Redux DevTools`](https://github.com/zalmoxisus/remote-redux-devtools)'s store enhancer, and from the extension's context menu choose 'Open Remote DevTools' for remote monitoring.
|
||||||
|
|
||||||
## 2. Without Redux
|
## 2. Without Redux
|
||||||
|
|
||||||
See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details on how to use the extension with any architecture.
|
See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details on how to use the extension with any architecture.
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
- [Options (arguments)](docs/API/Arguments.md)
|
- [Options (arguments)](docs/API/Arguments.md)
|
||||||
- [Methods (advanced API)](docs/API/Methods.md)
|
- [Methods (advanced API)](docs/API/Methods.md)
|
||||||
- [FAQ](docs/FAQ.md)
|
- [FAQ](docs/FAQ.md)
|
||||||
|
@ -191,6 +235,7 @@ See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/
|
||||||
- [Feedback](docs/Feedback.md)
|
- [Feedback](docs/Feedback.md)
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
Live demos to use the extension with:
|
Live demos to use the extension with:
|
||||||
|
|
||||||
- [Counter](http://zalmoxisus.github.io/examples/counter/)
|
- [Counter](http://zalmoxisus.github.io/examples/counter/)
|
||||||
|
@ -202,6 +247,7 @@ Live demos to use the extension with:
|
||||||
Also see [`./examples` folder](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/examples).
|
Also see [`./examples` folder](https://github.com/zalmoxisus/redux-devtools-extension/tree/master/examples).
|
||||||
|
|
||||||
## Backers
|
## Backers
|
||||||
|
|
||||||
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
|
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/redux-devtools-extension#backer)]
|
||||||
|
|
||||||
<a href="https://opencollective.com/redux-devtools-extension/backer/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/0/avatar.svg"></a>
|
<a href="https://opencollective.com/redux-devtools-extension/backer/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/0/avatar.svg"></a>
|
||||||
|
@ -235,8 +281,8 @@ Support us with a monthly donation and help us continue our activities. [[Become
|
||||||
<a href="https://opencollective.com/redux-devtools-extension/backer/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/28/avatar.svg"></a>
|
<a href="https://opencollective.com/redux-devtools-extension/backer/28/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/28/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/redux-devtools-extension/backer/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/29/avatar.svg"></a>
|
<a href="https://opencollective.com/redux-devtools-extension/backer/29/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/backer/29/avatar.svg"></a>
|
||||||
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
|
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/redux-devtools-extension#sponsor)]
|
||||||
|
|
||||||
<a href="https://opencollective.com/redux-devtools-extension/sponsor/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/0/avatar.svg"></a>
|
<a href="https://opencollective.com/redux-devtools-extension/sponsor/0/website" target="_blank"><img src="https://opencollective.com/redux-devtools-extension/sponsor/0/avatar.svg"></a>
|
||||||
|
|
|
@ -3,7 +3,7 @@ environment:
|
||||||
- nodejs_version: '6'
|
- nodejs_version: '6'
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- "%LOCALAPPDATA%/Yarn"
|
- '%LOCALAPPDATA%/Yarn'
|
||||||
- node_modules
|
- node_modules
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
|
|
@ -1,98 +1,141 @@
|
||||||
# Options
|
# Options
|
||||||
|
|
||||||
Use with
|
Use with
|
||||||
|
|
||||||
- `window.__REDUX_DEVTOOLS_EXTENSION__([options])`
|
- `window.__REDUX_DEVTOOLS_EXTENSION__([options])`
|
||||||
- `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__([options])()`
|
- `window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__([options])()`
|
||||||
- `window.__REDUX_DEVTOOLS_EXTENSION__.connect([options])`
|
- `window.__REDUX_DEVTOOLS_EXTENSION__.connect([options])`
|
||||||
- `redux-devtools-extension` npm package:
|
- `redux-devtools-extension` npm package:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
|
|
||||||
const composeEnhancers = composeWithDevTools(options);
|
const composeEnhancers = composeWithDevTools(options);
|
||||||
const store = createStore(reducer, /* preloadedState, */ composeEnhancers(
|
const store = createStore(
|
||||||
applyMiddleware(...middleware),
|
reducer,
|
||||||
|
/* preloadedState, */ composeEnhancers(
|
||||||
|
applyMiddleware(...middleware)
|
||||||
// other store enhancers if any
|
// other store enhancers if any
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
The `options` object is optional, and can include any of the following.
|
The `options` object is optional, and can include any of the following.
|
||||||
|
|
||||||
### `name`
|
### `name`
|
||||||
*string* - the instance name to be shown on the monitor page. Default value is `document.title`. If not specified and there's no document title, it will consist of `tabId` and `instanceId`.
|
|
||||||
|
_string_ - the instance name to be shown on the monitor page. Default value is `document.title`. If not specified and there's no document title, it will consist of `tabId` and `instanceId`.
|
||||||
|
|
||||||
### `actionCreators`
|
### `actionCreators`
|
||||||
*array* or *object* - action creators functions to be available in the Dispatcher. See [the example](https://github.com/zalmoxisus/redux-devtools-extension/commit/477e69d8649dfcdc9bf84dd45605dab7d9775c03).
|
|
||||||
|
_array_ or _object_ - action creators functions to be available in the Dispatcher. See [the example](https://github.com/zalmoxisus/redux-devtools-extension/commit/477e69d8649dfcdc9bf84dd45605dab7d9775c03).
|
||||||
|
|
||||||
### `latency`
|
### `latency`
|
||||||
*number (in ms)* - if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once. It is the joint between performance and speed. When set to `0`, all actions will be sent instantly. Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value). Default is `500 ms`.
|
|
||||||
|
_number (in ms)_ - if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once. It is the joint between performance and speed. When set to `0`, all actions will be sent instantly. Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value). Default is `500 ms`.
|
||||||
|
|
||||||
### `maxAge`
|
### `maxAge`
|
||||||
*number* (>1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. Default is `50`.
|
|
||||||
|
_number_ (>1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. Default is `50`.
|
||||||
|
|
||||||
### `trace`
|
### `trace`
|
||||||
*boolean* or *function* - if set to `true`, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code ([more details](../Features/Trace.md)). You can use a function (with action object as argument) which should return `new Error().stack` string, getting the stack outside of reducers. Default to `false`.
|
|
||||||
|
_boolean_ or _function_ - if set to `true`, will include stack trace for every dispatched action, so you can see it in trace tab jumping directly to that part of code ([more details](../Features/Trace.md)). You can use a function (with action object as argument) which should return `new Error().stack` string, getting the stack outside of reducers. Default to `false`.
|
||||||
|
|
||||||
### `traceLimit`
|
### `traceLimit`
|
||||||
*number* - maximum stack trace frames to be stored (in case `trace` option was provided as `true`). By default it's `10`. Note that, because extension's calls are excluded, the resulted frames could be 1 less. If `trace` option is a function, `traceLimit` will have no effect, as it's supposed to be handled there.
|
|
||||||
|
_number_ - maximum stack trace frames to be stored (in case `trace` option was provided as `true`). By default it's `10`. Note that, because extension's calls are excluded, the resulted frames could be 1 less. If `trace` option is a function, `traceLimit` will have no effect, as it's supposed to be handled there.
|
||||||
|
|
||||||
### `serialize`
|
### `serialize`
|
||||||
*boolean* or *object* which contains:
|
|
||||||
|
_boolean_ or _object_ which contains:
|
||||||
|
|
||||||
- **options** `object or boolean`:
|
- **options** `object or boolean`:
|
||||||
|
|
||||||
- `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
|
- `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode).
|
||||||
- `false` - will handle also circular references.
|
- `false` - will handle also circular references.
|
||||||
- `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions.
|
- `true` - will handle also date, regex, undefined, primitives, error objects, symbols, maps, sets and functions.
|
||||||
- object, which contains `date`, `regex`, `undefined`, `nan`, `infinity`, `error`, `symbol`, `map`, `set` and `function` keys. For each of them you can indicate if to include (by setting as `true`). For `function` key you can also specify a custom function which handles serialization. See [`jsan`](https://github.com/kolodny/jsan) for more details. Example:
|
- object, which contains `date`, `regex`, `undefined`, `nan`, `infinity`, `error`, `symbol`, `map`, `set` and `function` keys. For each of them you can indicate if to include (by setting as `true`). For `function` key you can also specify a custom function which handles serialization. See [`jsan`](https://github.com/kolodny/jsan) for more details. Example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = Redux.createStore(
|
||||||
|
reducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
serialize: {
|
serialize: {
|
||||||
options: {
|
options: {
|
||||||
undefined: true,
|
undefined: true,
|
||||||
function: function(fn) { return fn.toString() }
|
function: function (fn) {
|
||||||
}
|
return fn.toString();
|
||||||
}
|
},
|
||||||
}));
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **replacer** `function(key, value)` - [JSON `replacer` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) used for both actions and states stringify.
|
- **replacer** `function(key, value)` - [JSON `replacer` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter) used for both actions and states stringify.
|
||||||
|
|
||||||
Example of usage with [mori data structures](https://github.com/swannodette/mori):
|
Example of usage with [mori data structures](https://github.com/swannodette/mori):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = Redux.createStore(
|
||||||
|
reducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
serialize: {
|
serialize: {
|
||||||
replacer: (key, value) => value && mori.isMap(value) ? mori.toJs(value) : value
|
replacer: (key, value) =>
|
||||||
}
|
value && mori.isMap(value) ? mori.toJs(value) : value,
|
||||||
}));
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
In addition, you can specify a data type by adding a [`__serializedType__`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/helpers/index.js#L4) key. So you can deserialize it back while importing or persisting data. Moreover, it will also [show a nice preview showing the provided custom type](https://cloud.githubusercontent.com/assets/7957859/21814330/a17d556a-d761-11e6-85ef-159dd12f36c5.png):
|
In addition, you can specify a data type by adding a [`__serializedType__`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/helpers/index.js#L4) key. So you can deserialize it back while importing or persisting data. Moreover, it will also [show a nice preview showing the provided custom type](https://cloud.githubusercontent.com/assets/7957859/21814330/a17d556a-d761-11e6-85ef-159dd12f36c5.png):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = Redux.createStore(
|
||||||
|
reducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
serialize: {
|
serialize: {
|
||||||
replacer: (key, value) => {
|
replacer: (key, value) => {
|
||||||
if (Immutable.List.isList(value)) { // use your custom data type checker
|
if (Immutable.List.isList(value)) {
|
||||||
|
// use your custom data type checker
|
||||||
return {
|
return {
|
||||||
data: value.toArray(), // ImmutableJS custom method to get JS data as array
|
data: value.toArray(), // ImmutableJS custom method to get JS data as array
|
||||||
__serializedType__: 'ImmutableList' // mark you custom data type to show and retrieve back
|
__serializedType__: 'ImmutableList', // mark you custom data type to show and retrieve back
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
})
|
||||||
}));
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **reviver** `function(key, value)` - [JSON `reviver` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) used for parsing the imported actions and states. See [`remotedev-serialize`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/immutable/serialize.js#L8-L41) as an example on how to serialize special data types and get them back:
|
- **reviver** `function(key, value)` - [JSON `reviver` function](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#Using_the_reviver_parameter) used for parsing the imported actions and states. See [`remotedev-serialize`](https://github.com/zalmoxisus/remotedev-serialize/blob/master/immutable/serialize.js#L8-L41) as an example on how to serialize special data types and get them back:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = Redux.createStore(
|
||||||
|
reducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
serialize: {
|
serialize: {
|
||||||
reviver: (key, value) => {
|
reviver: (key, value) => {
|
||||||
if (typeof value === 'object' && value !== null && '__serializedType__' in value) {
|
if (
|
||||||
|
typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
'__serializedType__' in value
|
||||||
|
) {
|
||||||
switch (value.__serializedType__) {
|
switch (value.__serializedType__) {
|
||||||
case 'ImmutableList': return Immutable.List(value.data);
|
case 'ImmutableList':
|
||||||
|
return Immutable.List(value.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **immutable** `object` - automatically serialize/deserialize immutablejs via [remotedev-serialize](https://github.com/zalmoxisus/remotedev-serialize). Just pass the Immutable library like so:
|
- **immutable** `object` - automatically serialize/deserialize immutablejs via [remotedev-serialize](https://github.com/zalmoxisus/remotedev-serialize). Just pass the Immutable library like so:
|
||||||
|
@ -101,15 +144,21 @@ The `options` object is optional, and can include any of the following.
|
||||||
import Immutable from 'immutable'; // https://facebook.github.io/immutable-js/
|
import Immutable from 'immutable'; // https://facebook.github.io/immutable-js/
|
||||||
// ...
|
// ...
|
||||||
// Like above, only showing off compose this time. Reminder you might not want this in prod.
|
// Like above, only showing off compose this time. Reminder you might not want this in prod.
|
||||||
const composeEnhancers = typeof window === 'object' && typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ !== 'undefined' ?
|
const composeEnhancers =
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
typeof window === 'object' &&
|
||||||
|
typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ !== 'undefined'
|
||||||
|
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||||
serialize: {
|
serialize: {
|
||||||
immutable: Immutable
|
immutable: Immutable,
|
||||||
}
|
},
|
||||||
}) : compose;
|
})
|
||||||
|
: compose;
|
||||||
```
|
```
|
||||||
|
|
||||||
It will support all ImmutableJS structures. You can even export them into a file and get them back. The only exception is `Record` class, for which you should pass in addition the references to your classes in `refs`.
|
It will support all ImmutableJS structures. You can even export them into a file and get them back. The only exception is `Record` class, for which you should pass in addition the references to your classes in `refs`.
|
||||||
|
|
||||||
- **refs** `array` - ImmutableJS `Record` classes used to make possible restore its instances back when importing, persisting... Example of usage:
|
- **refs** `array` - ImmutableJS `Record` classes used to make possible restore its instances back when importing, persisting... Example of usage:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
// ...
|
// ...
|
||||||
|
@ -117,102 +166,142 @@ The `options` object is optional, and can include any of the following.
|
||||||
const ABRecord = Immutable.Record({ a: 1, b: 2 });
|
const ABRecord = Immutable.Record({ a: 1, b: 2 });
|
||||||
const myRecord = new ABRecord({ b: 3 }); // used in the reducers
|
const myRecord = new ABRecord({ b: 3 }); // used in the reducers
|
||||||
|
|
||||||
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = createStore(
|
||||||
|
rootReducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
serialize: {
|
serialize: {
|
||||||
immutable: Immutable,
|
immutable: Immutable,
|
||||||
refs: [ABRecord]
|
refs: [ABRecord],
|
||||||
}
|
},
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Also you can specify alternative values right in the state object (in the initial state of the reducer) by adding `toJSON` function:
|
Also you can specify alternative values right in the state object (in the initial state of the reducer) by adding `toJSON` function:
|
||||||
|
|
||||||
In the example bellow it will always send `{ component: '[React]' }`, regardless of the state's `component` value (useful when you don't want to send lots of unnecessary data):
|
In the example bellow it will always send `{ component: '[React]' }`, regardless of the state's `component` value (useful when you don't want to send lots of unnecessary data):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function component(
|
function component(
|
||||||
state = { component: null, toJSON: () => ({ component: '[React]' }) },
|
state = { component: null, toJSON: () => ({ component: '[React]' }) },
|
||||||
action
|
action
|
||||||
) {
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_COMPONENT': return { component: action.component };
|
case 'ADD_COMPONENT':
|
||||||
default: return state;
|
return { component: action.component };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You could also alter the value. For example when state is `{ count: 1 }`, we'll send `{ counter: 10 }` (notice we don't have an arrow function this time to use the object's `this`):
|
You could also alter the value. For example when state is `{ count: 1 }`, we'll send `{ counter: 10 }` (notice we don't have an arrow function this time to use the object's `this`):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function counter(
|
function counter(
|
||||||
state = { count: 0, toJSON: function (){ return { conter: this.count * 10 }; } },
|
state = {
|
||||||
|
count: 0,
|
||||||
|
toJSON: function () {
|
||||||
|
return { conter: this.count * 10 };
|
||||||
|
},
|
||||||
|
},
|
||||||
action
|
action
|
||||||
) {
|
) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'INCREMENT': return { count: state.count + 1 };
|
case 'INCREMENT':
|
||||||
default: return state;
|
return { count: state.count + 1 };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `actionSanitizer` / `stateSanitizer`
|
### `actionSanitizer` / `stateSanitizer`
|
||||||
- **actionSanitizer** (*function*) - function which takes `action` object and id number as arguments, and should return `action` object back. See the example bellow.
|
|
||||||
- **stateSanitizer** (*function*) - function which takes `state` object and index as arguments, and should return `state` object back.
|
- **actionSanitizer** (_function_) - function which takes `action` object and id number as arguments, and should return `action` object back. See the example bellow.
|
||||||
|
- **stateSanitizer** (_function_) - function which takes `state` object and index as arguments, and should return `state` object back.
|
||||||
|
|
||||||
Example of usage:
|
Example of usage:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const actionSanitizer = (action) => (
|
const actionSanitizer = (action) =>
|
||||||
action.type === 'FILE_DOWNLOAD_SUCCESS' && action.data ?
|
action.type === 'FILE_DOWNLOAD_SUCCESS' && action.data
|
||||||
{ ...action, data: '<<LONG_BLOB>>' } : action
|
? { ...action, data: '<<LONG_BLOB>>' }
|
||||||
);
|
: action;
|
||||||
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = createStore(
|
||||||
|
rootReducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
actionSanitizer,
|
actionSanitizer,
|
||||||
stateSanitizer: (state) => state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
|
stateSanitizer: (state) =>
|
||||||
}));
|
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### `actionsBlacklist` / `actionsWhitelist`
|
### `actionsBlacklist` / `actionsWhitelist`
|
||||||
*string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). If `actionsWhitelist` specified, `actionsBlacklist` is ignored.
|
|
||||||
|
_string or array of strings as regex_ - actions types to be hidden / shown in the monitors (while passed to the reducers). If `actionsWhitelist` specified, `actionsBlacklist` is ignored.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
createStore(reducer, remotedev({
|
createStore(
|
||||||
|
reducer,
|
||||||
|
remotedev({
|
||||||
sendTo: 'http://localhost:8000',
|
sendTo: 'http://localhost:8000',
|
||||||
actionsBlacklist: 'SOME_ACTION'
|
actionsBlacklist: 'SOME_ACTION',
|
||||||
// or actionsBlacklist: ['SOME_ACTION', 'SOME_OTHER_ACTION']
|
// or actionsBlacklist: ['SOME_ACTION', 'SOME_OTHER_ACTION']
|
||||||
// or just actionsBlacklist: 'SOME_' to omit both
|
// or just actionsBlacklist: 'SOME_' to omit both
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### `predicate`
|
### `predicate`
|
||||||
*function* - called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor. Use it as a more advanced version of `actionsBlacklist`/`actionsWhitelist` parameters.
|
|
||||||
|
_function_ - called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor. Use it as a more advanced version of `actionsBlacklist`/`actionsWhitelist` parameters.
|
||||||
Example of usage:
|
Example of usage:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = createStore(
|
||||||
predicate: (state, action) => state.dev.logLevel === VERBOSE && !action.forwarded
|
rootReducer,
|
||||||
}));
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
|
predicate: (state, action) =>
|
||||||
|
state.dev.logLevel === VERBOSE && !action.forwarded,
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### `shouldRecordChanges`
|
### `shouldRecordChanges`
|
||||||
*boolean* - if specified as `false`, it will not record the changes till clicking on `Start recording` button. Default is `true`. Available only for Redux enhancer, for others use `autoPause`.
|
|
||||||
|
_boolean_ - if specified as `false`, it will not record the changes till clicking on `Start recording` button. Default is `true`. Available only for Redux enhancer, for others use `autoPause`.
|
||||||
|
|
||||||
### `pauseActionType`
|
### `pauseActionType`
|
||||||
*string* - if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type. If not specified, will commit when paused. Available only for Redux enhancer. Default is `@@PAUSED`.
|
|
||||||
|
_string_ - if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type. If not specified, will commit when paused. Available only for Redux enhancer. Default is `@@PAUSED`.
|
||||||
|
|
||||||
### `autoPause`
|
### `autoPause`
|
||||||
*boolean* - auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use. Not available for Redux enhancer (as it already does it but storing the data to be sent). Default is `false`.
|
|
||||||
|
_boolean_ - auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use. Not available for Redux enhancer (as it already does it but storing the data to be sent). Default is `false`.
|
||||||
|
|
||||||
### `shouldStartLocked`
|
### `shouldStartLocked`
|
||||||
*boolean* - if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button. Available only for Redux enhancer. Default is `false`.
|
|
||||||
|
_boolean_ - if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button. Available only for Redux enhancer. Default is `false`.
|
||||||
|
|
||||||
### `shouldHotReload`
|
### `shouldHotReload`
|
||||||
*boolean* - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer. Default to `true`.
|
|
||||||
|
_boolean_ - if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer. Default to `true`.
|
||||||
|
|
||||||
### `shouldCatchErrors`
|
### `shouldCatchErrors`
|
||||||
*boolean* - if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched.
|
|
||||||
|
_boolean_ - if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched.
|
||||||
|
|
||||||
### `features`
|
### `features`
|
||||||
|
|
||||||
If you want to restrict the extension, just specify the features you allow:
|
If you want to restrict the extension, just specify the features you allow:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const composeEnhancers = composeWithDevTools({
|
const composeEnhancers = composeWithDevTools({
|
||||||
features: {
|
features: {
|
||||||
|
@ -225,10 +314,11 @@ const composeEnhancers = composeWithDevTools({
|
||||||
skip: true, // skip (cancel) actions
|
skip: true, // skip (cancel) actions
|
||||||
reorder: true, // drag and drop actions in the history list
|
reorder: true, // drag and drop actions in the history list
|
||||||
dispatch: true, // dispatch custom actions or action creators
|
dispatch: true, // dispatch custom actions or action creators
|
||||||
test: true // generate tests for the selected actions
|
test: true, // generate tests for the selected actions
|
||||||
},
|
},
|
||||||
// other options like actionSanitizer, stateSanitizer
|
// other options like actionSanitizer, stateSanitizer
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed.
|
If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed.
|
||||||
Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side. Otherwise, you'll get/set the data right from the monitor part.
|
Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side. Otherwise, you'll get/set the data right from the monitor part.
|
||||||
|
|
|
@ -12,14 +12,16 @@ Use the following methods of `window.__REDUX_DEVTOOLS_EXTENSION__`:
|
||||||
- [notifyErrors](#notifyerrors)
|
- [notifyErrors](#notifyerrors)
|
||||||
|
|
||||||
<a id="connect"></a>
|
<a id="connect"></a>
|
||||||
|
|
||||||
### connect([options])
|
### connect([options])
|
||||||
|
|
||||||
##### Arguments
|
##### Arguments
|
||||||
|
|
||||||
- [`options`] *Object* - [see the available options](Arguments.md).
|
- [`options`] _Object_ - [see the available options](Arguments.md).
|
||||||
|
|
||||||
##### Returns
|
##### Returns
|
||||||
*Object* containing the following methods:
|
|
||||||
|
_Object_ containing the following methods:
|
||||||
|
|
||||||
- `subscribe(listener)` - adds a change listener. It will be called any time an action is dispatched from the monitor. Returns a function to unsubscribe the current listener.
|
- `subscribe(listener)` - adds a change listener. It will be called any time an action is dispatched from the monitor. Returns a function to unsubscribe the current listener.
|
||||||
- `unsubscribe()` - unsubscribes all listeners.
|
- `unsubscribe()` - unsubscribes all listeners.
|
||||||
|
@ -37,7 +39,7 @@ devTools.subscribe((message) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
devTools.init({ value: 'initial state' });
|
devTools.init({ value: 'initial state' });
|
||||||
devTools.send('change state', { value: 'state changed' })
|
devTools.send('change state', { value: 'state changed' });
|
||||||
```
|
```
|
||||||
|
|
||||||
See [redux enhancer's example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
|
See [redux enhancer's example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/npm-package/logOnly.js), [react example](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/examples/react-counter-messaging/components/Counter.js) and [blog post](https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f) for more details.
|
||||||
|
@ -47,41 +49,45 @@ See [redux enhancer's example](https://github.com/zalmoxisus/redux-devtools-exte
|
||||||
Remove extensions listener and disconnect extensions background script connection. Usually just unsubscribing the listener inside the `connect` is enough.
|
Remove extensions listener and disconnect extensions background script connection. Usually just unsubscribing the listener inside the `connect` is enough.
|
||||||
|
|
||||||
<a id="send"></a>
|
<a id="send"></a>
|
||||||
|
|
||||||
### send(action, state, [options, instanceId])
|
### send(action, state, [options, instanceId])
|
||||||
|
|
||||||
Send a new action and state manually to be shown on the monitor. It's recommended to use [`connect`](connect), unless you want to hook into an already created instance.
|
Send a new action and state manually to be shown on the monitor. It's recommended to use [`connect`](connect), unless you want to hook into an already created instance.
|
||||||
|
|
||||||
##### Arguments
|
##### Arguments
|
||||||
|
|
||||||
- `action` *String* (action type) or *Object* with required `type` key.
|
- `action` _String_ (action type) or _Object_ with required `type` key.
|
||||||
- `state` *any* - usually object to expand.
|
- `state` _any_ - usually object to expand.
|
||||||
- [`options`] *Object* - [see the available options](Arguments.md).
|
- [`options`] _Object_ - [see the available options](Arguments.md).
|
||||||
- [`instanceId`] *String* - instance id for which to include the log. If not specified and not present in the `options` object, will be the first available instance.
|
- [`instanceId`] _String_ - instance id for which to include the log. If not specified and not present in the `options` object, will be the first available instance.
|
||||||
|
|
||||||
<a id="listen"></a>
|
<a id="listen"></a>
|
||||||
|
|
||||||
### listen(onMessage, instanceId)
|
### listen(onMessage, instanceId)
|
||||||
|
|
||||||
Listen for messages dispatched for specific `instanceId`. For most cases it's better to use `subcribe` inside the [`connect`](connect).
|
Listen for messages dispatched for specific `instanceId`. For most cases it's better to use `subcribe` inside the [`connect`](connect).
|
||||||
|
|
||||||
##### Arguments
|
##### Arguments
|
||||||
|
|
||||||
- `onMessage` *Function* to call when there's an action from the monitor.
|
- `onMessage` _Function_ to call when there's an action from the monitor.
|
||||||
- `instanceId` *String* - instance id for which to handle actions.
|
- `instanceId` _String_ - instance id for which to handle actions.
|
||||||
|
|
||||||
<a id="open"></a>
|
<a id="open"></a>
|
||||||
|
|
||||||
### open([position])
|
### open([position])
|
||||||
|
|
||||||
Open the extension's window. This should be conditional (usually you don't need to open extension's window automatically).
|
Open the extension's window. This should be conditional (usually you don't need to open extension's window automatically).
|
||||||
|
|
||||||
##### Arguments
|
##### Arguments
|
||||||
|
|
||||||
- [`position`] *String* - window position: `left`, `right`, `bottom`. Also can be `panel` to [open it in a Chrome panel](../FAQ.md#how-to-keep-devtools-window-focused-all-the-time-in-a-chrome-panel). Or `remote` to [open remote monitor](../FAQ.md#how-to-get-it-work-with-webworkers-react-native-hybrid-desktop-and-server-side-apps). By default is `left`.
|
- [`position`] _String_ - window position: `left`, `right`, `bottom`. Also can be `panel` to [open it in a Chrome panel](../FAQ.md#how-to-keep-devtools-window-focused-all-the-time-in-a-chrome-panel). Or `remote` to [open remote monitor](../FAQ.md#how-to-get-it-work-with-webworkers-react-native-hybrid-desktop-and-server-side-apps). By default is `left`.
|
||||||
|
|
||||||
<a id="notifyErrors"></a>
|
<a id="notifyErrors"></a>
|
||||||
|
|
||||||
### notifyErrors([onError])
|
### notifyErrors([onError])
|
||||||
|
|
||||||
When called, the extension will listen for uncaught exceptions on the page, and, if any, will show native notifications. Optionally, you can provide a function to be called when an exception occurs.
|
When called, the extension will listen for uncaught exceptions on the page, and, if any, will show native notifications. Optionally, you can provide a function to be called when an exception occurs.
|
||||||
|
|
||||||
##### Arguments
|
##### Arguments
|
||||||
|
|
||||||
- [`onError`] *Function* to call when there's an exceptions.
|
- [`onError`] _Function_ to call when there's an exceptions.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Redux DevTools Extension FAQ
|
# Redux DevTools Extension FAQ
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [How to get it work](#how-to-get-it-work)
|
- [How to get it work](#how-to-get-it-work)
|
||||||
- [How to disable/enable it in production](#how-to-disable-it-in-production)
|
- [How to disable/enable it in production](#how-to-disable-it-in-production)
|
||||||
- [How to persist debug sessions across page reloads](#how-to-persist-debug-sessions-across-page-reloads)
|
- [How to persist debug sessions across page reloads](#how-to-persist-debug-sessions-across-page-reloads)
|
||||||
|
@ -10,22 +11,35 @@
|
||||||
- [Keyboard shortcuts](#keyboard-shortcuts)
|
- [Keyboard shortcuts](#keyboard-shortcuts)
|
||||||
|
|
||||||
#### How to get it work
|
#### How to get it work
|
||||||
|
|
||||||
- Check the extension with [Counter](http://zalmoxisus.github.io/examples/counter/) or [TodoMVC](http://zalmoxisus.github.io/examples/todomvc/) demo.
|
- Check the extension with [Counter](http://zalmoxisus.github.io/examples/counter/) or [TodoMVC](http://zalmoxisus.github.io/examples/todomvc/) demo.
|
||||||
- Reload the extension on the extensions page (`chrome://extensions/`).
|
- Reload the extension on the extensions page (`chrome://extensions/`).
|
||||||
- If something goes wrong, [open an issue](https://github.com/zalmoxisus/redux-devtools-extension/issues) or tweet me: [@mdiordiev](https://twitter.com/mdiordiev).
|
- If something goes wrong, [open an issue](https://github.com/zalmoxisus/redux-devtools-extension/issues) or tweet me: [@mdiordiev](https://twitter.com/mdiordiev).
|
||||||
|
|
||||||
#### How to disable it in production
|
#### How to disable it in production
|
||||||
|
|
||||||
Usually you don't have to. See [the article for details on how to include it in production](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
|
Usually you don't have to. See [the article for details on how to include it in production](https://medium.com/@zalmoxis/using-redux-devtools-in-production-4c5b56c5600f).
|
||||||
|
|
||||||
#### How to persist debug sessions across page reloads
|
#### How to persist debug sessions across page reloads
|
||||||
|
|
||||||
Just click the `Persist` button or add `?debug_session=<session_name>` to the url.
|
Just click the `Persist` button or add `?debug_session=<session_name>` to the url.
|
||||||
|
|
||||||
#### How to open DevTools programmatically
|
#### How to open DevTools programmatically
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__.open();
|
window.__REDUX_DEVTOOLS_EXTENSION__.open();
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure to have it conditionally. Auto opening windows is a bad DX. See the [API](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Methods.md#open) for details.
|
Make sure to have it conditionally. Auto opening windows is a bad DX. See the [API](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Methods.md#open) for details.
|
||||||
|
|
||||||
#### How to enable/disable errors notifying
|
#### How to enable/disable errors notifying
|
||||||
|
|
||||||
Just find `Redux DevTools` on the extensions page (`chrome://extensions/`) and click the `Options` link to customize everything. The errors notifying is disabled by default. If enabled, it works only when the store enhancer is called (in order not to show notifications for any sites you visit). In case you want notifications for a non-redux app, init it explicitly by calling `window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors()` (probably you'll check if `window.__REDUX_DEVTOOLS_EXTENSION__` exists before calling it).
|
Just find `Redux DevTools` on the extensions page (`chrome://extensions/`) and click the `Options` link to customize everything. The errors notifying is disabled by default. If enabled, it works only when the store enhancer is called (in order not to show notifications for any sites you visit). In case you want notifications for a non-redux app, init it explicitly by calling `window.__REDUX_DEVTOOLS_EXTENSION__.notifyErrors()` (probably you'll check if `window.__REDUX_DEVTOOLS_EXTENSION__` exists before calling it).
|
||||||
|
|
||||||
#### How to get it work with WebWorkers, React Native, hybrid, desktop and server side apps
|
#### How to get it work with WebWorkers, React Native, hybrid, desktop and server side apps
|
||||||
|
|
||||||
It is not possible to inject extension's script there and to communicate directly. To solve this, use [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools). After including it inside the app, click `Remote` button for remote monitoring.
|
It is not possible to inject extension's script there and to communicate directly. To solve this, use [Remote Redux DevTools](https://github.com/zalmoxisus/remote-redux-devtools). After including it inside the app, click `Remote` button for remote monitoring.
|
||||||
|
|
||||||
#### Keyboard shortcuts
|
#### Keyboard shortcuts
|
||||||
|
|
||||||
To set/change the keyboard shortcuts, click "Keyboard shortcuts" button on the bottom of the extensions page (`chrome://extensions/`). By default only `Cmd` (`Ctrl`) + `Shift` + `E` is available, which will open the extension popup (only when the Redux store is available in the current page).
|
To set/change the keyboard shortcuts, click "Keyboard shortcuts" button on the bottom of the extensions page (`chrome://extensions/`). By default only `Cmd` (`Ctrl`) + `Shift` + `E` is available, which will open the extension popup (only when the Redux store is available in the current page).
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Integrations for js and non-js frameworks
|
# Integrations for js and non-js frameworks
|
||||||
|
|
||||||
Mostly functional:
|
Mostly functional:
|
||||||
|
|
||||||
- [React](#react)
|
- [React](#react)
|
||||||
- [Angular](#angular)
|
- [Angular](#angular)
|
||||||
- [Cycle](#cycle)
|
- [Cycle](#cycle)
|
||||||
|
@ -13,26 +14,32 @@ Mostly functional:
|
||||||
- [Aurelia](#aurelia)
|
- [Aurelia](#aurelia)
|
||||||
|
|
||||||
In progress:
|
In progress:
|
||||||
|
|
||||||
- [ClojureScript](#clojurescript)
|
- [ClojureScript](#clojurescript)
|
||||||
- [Horizon](#horizon)
|
- [Horizon](#horizon)
|
||||||
- [Python](#python)
|
- [Python](#python)
|
||||||
- [Swift](#swift)
|
- [Swift](#swift)
|
||||||
|
|
||||||
### [React](https://github.com/facebook/react)
|
### [React](https://github.com/facebook/react)
|
||||||
|
|
||||||
#### Inspect React props
|
#### Inspect React props
|
||||||
|
|
||||||
##### [`react-inspect-props`](https://github.com/lucasconstantino/react-inspect-props)
|
##### [`react-inspect-props`](https://github.com/lucasconstantino/react-inspect-props)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { compose, withState } from 'recompose'
|
import { compose, withState } from 'recompose';
|
||||||
import { inspectProps } from 'react-inspect-props'
|
import { inspectProps } from 'react-inspect-props';
|
||||||
|
|
||||||
compose(
|
compose(
|
||||||
withState('count', 'setCount', 0),
|
withState('count', 'setCount', 0),
|
||||||
inspectProps('Counter inspector')
|
inspectProps('Counter inspector')
|
||||||
)(Counter)
|
)(Counter);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Inspect React states
|
#### Inspect React states
|
||||||
|
|
||||||
##### [`remotedev-react-state`](https://github.com/jhen0409/remotedev-react-state)
|
##### [`remotedev-react-state`](https://github.com/jhen0409/remotedev-react-state)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import connectToDevTools from 'remotedev-react-state'
|
import connectToDevTools from 'remotedev-react-state'
|
||||||
|
|
||||||
|
@ -43,18 +50,22 @@ componentWillMount() {
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Inspect React hooks (useState and useReducer)
|
#### Inspect React hooks (useState and useReducer)
|
||||||
|
|
||||||
##### [`reinspect`](https://github.com/troch/reinspect)
|
##### [`reinspect`](https://github.com/troch/reinspect)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { useState } from 'reinspect'
|
import { useState } from 'reinspect';
|
||||||
|
|
||||||
export function CounterWithUseState({ id }) {
|
export function CounterWithUseState({ id }) {
|
||||||
const [count, setCount] = useState(0, id)
|
const [count, setCount] = useState(0, id);
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Mobx](https://github.com/mobxjs/mobx)
|
### [Mobx](https://github.com/mobxjs/mobx)
|
||||||
|
|
||||||
#### [`mobx-remotedev`](https://github.com/zalmoxisus/mobx-remotedev)
|
#### [`mobx-remotedev`](https://github.com/zalmoxisus/mobx-remotedev)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import remotedev from 'mobx-remotedev';
|
import remotedev from 'mobx-remotedev';
|
||||||
// or import remotedev from 'mobx-remotedev/lib/dev'
|
// or import remotedev from 'mobx-remotedev/lib/dev'
|
||||||
|
@ -70,10 +81,12 @@ class appStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default remotedev(appStore);
|
export default remotedev(appStore);
|
||||||
````
|
```
|
||||||
|
|
||||||
### [Angular](https://github.com/angular/angular)
|
### [Angular](https://github.com/angular/angular)
|
||||||
|
|
||||||
#### [ng2-redux](https://github.com/angular-redux/ng2-redux)
|
#### [ng2-redux](https://github.com/angular-redux/ng2-redux)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { NgReduxModule, NgRedux, DevToolsExtension } from 'ng2-redux';
|
import { NgReduxModule, NgRedux, DevToolsExtension } from 'ng2-redux';
|
||||||
|
|
||||||
|
@ -101,9 +114,11 @@ import { NgReduxModule, NgRedux, DevToolsExtension } from 'ng2-redux';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For Angular 1 see [ng-redux](https://github.com/angular-redux/ng-redux).
|
For Angular 1 see [ng-redux](https://github.com/angular-redux/ng-redux).
|
||||||
|
|
||||||
#### [Angular @ngrx/store](https://ngrx.io/) + [`@ngrx/store-devtools`](https://ngrx.io/guide/store-devtools)
|
#### [Angular @ngrx/store](https://ngrx.io/) + [`@ngrx/store-devtools`](https://ngrx.io/guide/store-devtools)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||||
|
|
||||||
|
@ -112,9 +127,9 @@ import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||||
StoreModule.forRoot(rootReducer),
|
StoreModule.forRoot(rootReducer),
|
||||||
// Instrumentation must be imported after importing StoreModule (config is optional)
|
// Instrumentation must be imported after importing StoreModule (config is optional)
|
||||||
StoreDevtoolsModule.instrument({
|
StoreDevtoolsModule.instrument({
|
||||||
maxAge: 5
|
maxAge: 5,
|
||||||
})
|
}),
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
```
|
```
|
||||||
|
@ -122,31 +137,45 @@ export class AppModule { }
|
||||||
[`Example of integration`](https://github.com/ngrx/platform/tree/master/projects/example-app/) ([live demo](https://ngrx.github.io/platform/example-app/)).
|
[`Example of integration`](https://github.com/ngrx/platform/tree/master/projects/example-app/) ([live demo](https://ngrx.github.io/platform/example-app/)).
|
||||||
|
|
||||||
### [Ember](http://emberjs.com/)
|
### [Ember](http://emberjs.com/)
|
||||||
|
|
||||||
#### [`ember-redux`](https://github.com/ember-redux/ember-redux)
|
#### [`ember-redux`](https://github.com/ember-redux/ember-redux)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//app/enhancers/index.js
|
//app/enhancers/index.js
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
var devtools = window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f;
|
var devtools = window.__REDUX_DEVTOOLS_EXTENSION__
|
||||||
|
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
|
: (f) => f;
|
||||||
export default compose(devtools);
|
export default compose(devtools);
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Cycle](https://github.com/cyclejs/cyclejs)
|
### [Cycle](https://github.com/cyclejs/cyclejs)
|
||||||
#### [`@culli/store`](https://github.com/milankinen/culli/tree/master/packages/store)
|
|
||||||
```js
|
|
||||||
import {run} from "@cycle/most-run"
|
|
||||||
import {makeDOMDriver as DOM} from "@cycle/dom"
|
|
||||||
import Store, {ReduxDevtools} from "@culli/store"
|
|
||||||
import App, {newId} from "./App"
|
|
||||||
|
|
||||||
|
#### [`@culli/store`](https://github.com/milankinen/culli/tree/master/packages/store)
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { run } from '@cycle/most-run';
|
||||||
|
import { makeDOMDriver as DOM } from '@cycle/dom';
|
||||||
|
import Store, { ReduxDevtools } from '@culli/store';
|
||||||
|
import App, { newId } from './App';
|
||||||
|
|
||||||
run(App, {
|
run(App, {
|
||||||
DOM: DOM("#app"),
|
DOM: DOM('#app'),
|
||||||
Store: Store(ReduxDevtools({items: [{id: newId(), num: 0}, {id: newId(), num: 0}]}))
|
Store: Store(
|
||||||
|
ReduxDevtools({
|
||||||
|
items: [
|
||||||
|
{ id: newId(), num: 0 },
|
||||||
|
{ id: newId(), num: 0 },
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
),
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Freezer](https://github.com/arqex/freezer)
|
### [Freezer](https://github.com/arqex/freezer)
|
||||||
|
|
||||||
#### [`freezer-redux-devtools`](https://github.com/arqex/freezer-redux-devtools)
|
#### [`freezer-redux-devtools`](https://github.com/arqex/freezer-redux-devtools)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { supportChromeExtension } from 'freezer-redux-devtools/freezer-redux-middleware';
|
import { supportChromeExtension } from 'freezer-redux-devtools/freezer-redux-middleware';
|
||||||
|
@ -160,7 +189,9 @@ supportChromeExtension( State );
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Horizon](https://github.com/rethinkdb/horizon)
|
### [Horizon](https://github.com/rethinkdb/horizon)
|
||||||
|
|
||||||
#### [`horizon-remotedev`](https://github.com/zalmoxisus/horizon-remotedev)
|
#### [`horizon-remotedev`](https://github.com/zalmoxisus/horizon-remotedev)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// import hzRemotedev from 'horizon-remotedev';
|
// import hzRemotedev from 'horizon-remotedev';
|
||||||
// or import hzRemotedev from 'horizon-remotedev/lib/dev'
|
// or import hzRemotedev from 'horizon-remotedev/lib/dev'
|
||||||
|
@ -171,11 +202,13 @@ const horizon = Horizon();
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
// Specify the horizon instance to monitor
|
// Specify the horizon instance to monitor
|
||||||
hzRemotedev(horizon("react_messages"))
|
hzRemotedev(horizon('react_messages'));
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Fable](https://github.com/fable-compiler/Fable)
|
### [Fable](https://github.com/fable-compiler/Fable)
|
||||||
|
|
||||||
#### [`fable-elmish/debugger`](https://github.com/fable-elmish/debugger)
|
#### [`fable-elmish/debugger`](https://github.com/fable-elmish/debugger)
|
||||||
|
|
||||||
```fsharp
|
```fsharp
|
||||||
open Elmish.Debug
|
open Elmish.Debug
|
||||||
|
|
||||||
|
@ -196,18 +229,25 @@ Program.mkProgram init update view
|
||||||
```
|
```
|
||||||
|
|
||||||
### [PureScript](https://github.com/purescript/purescript)
|
### [PureScript](https://github.com/purescript/purescript)
|
||||||
|
|
||||||
#### [`purescript-react-redux`](https://github.com/ethul/purescript-react-redux)
|
#### [`purescript-react-redux`](https://github.com/ethul/purescript-react-redux)
|
||||||
|
|
||||||
[`Example of integration`](https://github.com/ethul/purescript-react-redux-example).
|
[`Example of integration`](https://github.com/ethul/purescript-react-redux-example).
|
||||||
|
|
||||||
### [ClojureScript](https://github.com/clojure/clojurescript)
|
### [ClojureScript](https://github.com/clojure/clojurescript)
|
||||||
|
|
||||||
[`Example of integration`](http://gitlab.xet.ru:9999/publicpr/clojurescript-redux/tree/master#dev-setup)
|
[`Example of integration`](http://gitlab.xet.ru:9999/publicpr/clojurescript-redux/tree/master#dev-setup)
|
||||||
|
|
||||||
### [Python](https://www.python.org/)
|
### [Python](https://www.python.org/)
|
||||||
|
|
||||||
#### [`pyredux`](https://github.com/peterpeter5/pyredux)
|
#### [`pyredux`](https://github.com/peterpeter5/pyredux)
|
||||||
|
|
||||||
[WIP](https://github.com/zalmoxisus/remotedev-server/issues/34)
|
[WIP](https://github.com/zalmoxisus/remotedev-server/issues/34)
|
||||||
|
|
||||||
### [Swift](https://github.com/apple/swift)
|
### [Swift](https://github.com/apple/swift)
|
||||||
|
|
||||||
#### [`katanaMonitor`](https://github.com/bolismauro/katanaMonitor-lib-swift) for [`katana-swift`](https://github.com/BendingSpoons/katana-swift)
|
#### [`katanaMonitor`](https://github.com/bolismauro/katanaMonitor-lib-swift) for [`katana-swift`](https://github.com/BendingSpoons/katana-swift)
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
import KatanaMonitor
|
import KatanaMonitor
|
||||||
|
|
||||||
|
@ -221,7 +261,9 @@ middleware.append(MonitorMiddleware.create(using: .defaultConfiguration))
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Reductive](https://github.com/reasonml-community/reductive)
|
### [Reductive](https://github.com/reasonml-community/reductive)
|
||||||
|
|
||||||
#### [`reductive-dev-tools`](https://github.com/ambientlight/reductive-dev-tools)
|
#### [`reductive-dev-tools`](https://github.com/ambientlight/reductive-dev-tools)
|
||||||
|
|
||||||
```reason
|
```reason
|
||||||
let storeEnhancer =
|
let storeEnhancer =
|
||||||
ReductiveDevTools.(
|
ReductiveDevTools.(
|
||||||
|
@ -234,7 +276,9 @@ let storeCreator = storeEnhancer @@ Reductive.Store.create;
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Aurelia](http://aurelia.io)
|
### [Aurelia](http://aurelia.io)
|
||||||
|
|
||||||
#### [`aurelia-store`](https://aurelia.io/docs/plugins/store)
|
#### [`aurelia-store`](https://aurelia.io/docs/plugins/store)
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import {Aurelia} from 'aurelia-framework';
|
import {Aurelia} from 'aurelia-framework';
|
||||||
import {initialState} from './state';
|
import {initialState} from './state';
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
* [Extension](/README.md)
|
- [Extension](/README.md)
|
||||||
* [Installation](/README.md#installation)
|
- [Installation](/README.md#installation)
|
||||||
* [Usage](/README.md#usage)
|
- [Usage](/README.md#usage)
|
||||||
* [Demo](/README.md#demo)
|
- [Demo](/README.md#demo)
|
||||||
* [API Reference](/docs/API/README.md)
|
- [API Reference](/docs/API/README.md)
|
||||||
* [Options (arguments)](/docs/API/Arguments.md)
|
- [Options (arguments)](/docs/API/Arguments.md)
|
||||||
* [Methods (advanced API)](/docs/API/Methods.md)
|
- [Methods (advanced API)](/docs/API/Methods.md)
|
||||||
* Features
|
- Features
|
||||||
* [Trace actions calls](/docs/Features/Trace.md)
|
- [Trace actions calls](/docs/Features/Trace.md)
|
||||||
* [Integrations](/docs/Integrations.md)
|
- [Integrations](/docs/Integrations.md)
|
||||||
* [FAQ](/docs/FAQ.md)
|
- [FAQ](/docs/FAQ.md)
|
||||||
* [Troubleshooting](/docs/Troubleshooting.md)
|
- [Troubleshooting](/docs/Troubleshooting.md)
|
||||||
* [Recipes](/docs/Recipes.md)
|
- [Recipes](/docs/Recipes.md)
|
||||||
* [Articles](/docs/Articles.md)
|
- [Articles](/docs/Articles.md)
|
||||||
* [Videos](/docs/Videos.md)
|
- [Videos](/docs/Videos.md)
|
||||||
* [Credits](/docs/Credits.md)
|
- [Credits](/docs/Credits.md)
|
||||||
* [Support us](/README.md#backers)
|
- [Support us](/README.md#backers)
|
||||||
* [Feedback](/docs/Feedback.md)
|
- [Feedback](/docs/Feedback.md)
|
||||||
* [Change Log](https://github.com/zalmoxisus/redux-devtools-extension/releases)
|
- [Change Log](https://github.com/zalmoxisus/redux-devtools-extension/releases)
|
||||||
|
|
|
@ -12,37 +12,42 @@ const store = createStore(
|
||||||
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
|
(window as any).__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that you many need to set `no-any` to false in your `tslint.json` file.
|
Note that you many need to set `no-any` to false in your `tslint.json` file.
|
||||||
|
|
||||||
Alternatively you can use typeguard in order to avoid
|
Alternatively you can use typeguard in order to avoid
|
||||||
casting to any.
|
casting to any.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createStore, StoreEnhancer } from "redux";
|
import { createStore, StoreEnhancer } from 'redux';
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
type WindowWithDevTools = Window & {
|
type WindowWithDevTools = Window & {
|
||||||
__REDUX_DEVTOOLS_EXTENSION__: () => StoreEnhancer<unknown, {}>
|
__REDUX_DEVTOOLS_EXTENSION__: () => StoreEnhancer<unknown, {}>;
|
||||||
}
|
};
|
||||||
|
|
||||||
const isReduxDevtoolsExtenstionExist =
|
const isReduxDevtoolsExtenstionExist = (
|
||||||
(arg: Window | WindowWithDevTools):
|
arg: Window | WindowWithDevTools
|
||||||
arg is WindowWithDevTools => {
|
): arg is WindowWithDevTools => {
|
||||||
return '__REDUX_DEVTOOLS_EXTENSION__' in arg;
|
return '__REDUX_DEVTOOLS_EXTENSION__' in arg;
|
||||||
}
|
};
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
const store = createStore(rootReducer, initialState,
|
const store = createStore(
|
||||||
isReduxDevtoolsExtenstionExist(window) ?
|
rootReducer,
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__() : undefined)
|
initialState,
|
||||||
|
isReduxDevtoolsExtenstionExist(window)
|
||||||
|
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
|
: undefined
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Export from browser console or from application
|
### Export from browser console or from application
|
||||||
|
|
||||||
```js
|
```js
|
||||||
store.liftedStore.getState()
|
store.liftedStore.getState();
|
||||||
```
|
```
|
||||||
|
|
||||||
The extension is not sharing `store` object, so you should take care of that.
|
The extension is not sharing `store` object, so you should take care of that.
|
||||||
|
@ -55,16 +60,19 @@ We're [not allowing that from instrumentation part](https://github.com/zalmoxisu
|
||||||
import { createStore, compose } from 'redux';
|
import { createStore, compose } from 'redux';
|
||||||
import { devToolsEnhancer } from 'redux-devtools-extension/logOnly';
|
import { devToolsEnhancer } from 'redux-devtools-extension/logOnly';
|
||||||
|
|
||||||
const store = createStore(reducer, /* preloadedState, */ compose(
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
/* preloadedState, */ compose(
|
||||||
devToolsEnhancer({
|
devToolsEnhancer({
|
||||||
instaceID: 1,
|
instaceID: 1,
|
||||||
name: 'Blacklisted',
|
name: 'Blacklisted',
|
||||||
actionsBlacklist: '...'
|
actionsBlacklist: '...',
|
||||||
}),
|
}),
|
||||||
devToolsEnhancer({
|
devToolsEnhancer({
|
||||||
instaceID: 2,
|
instaceID: 2,
|
||||||
name: 'Whitelisted',
|
name: 'Whitelisted',
|
||||||
actionsWhitelist: '...'
|
actionsWhitelist: '...',
|
||||||
})
|
})
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
|
@ -25,11 +25,17 @@ Most likely you mutate the state. Check it by [adding `redux-immutable-state-inv
|
||||||
Usually the extension's store enhancer should be last in the compose. When you're using [`window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`](/README.md#12-advanced-store-setup) or [`composeWithDevTools`](/README.md#13-use-redux-devtools-extension-package-from-npm) helper you don't have to worry about the enhancers order. However some enhancers ([like `redux-batched-subscribe`](https://github.com/zalmoxisus/redux-devtools-extension/issues/261)) also have this requirement to be the last in the compose. In this case you can use it like so:
|
Usually the extension's store enhancer should be last in the compose. When you're using [`window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__`](/README.md#12-advanced-store-setup) or [`composeWithDevTools`](/README.md#13-use-redux-devtools-extension-package-from-npm) helper you don't have to worry about the enhancers order. However some enhancers ([like `redux-batched-subscribe`](https://github.com/zalmoxisus/redux-devtools-extension/issues/261)) also have this requirement to be the last in the compose. In this case you can use it like so:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = createStore(reducer, preloadedState, compose(
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
preloadedState,
|
||||||
|
compose(
|
||||||
// applyMiddleware(thunk),
|
// applyMiddleware(thunk),
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : noop => noop,
|
window.__REDUX_DEVTOOLS_EXTENSION__
|
||||||
|
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
|
: (noop) => noop,
|
||||||
batchedSubscribe(/* ... */)
|
batchedSubscribe(/* ... */)
|
||||||
));
|
)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `batchedSubscribe` is `redux-batched-subscribe` store enhancer.
|
Where `batchedSubscribe` is `redux-batched-subscribe` store enhancer.
|
||||||
|
@ -41,14 +47,19 @@ That is happening due to serialization of some huge objects included in the stat
|
||||||
You can do that by including/omitting data containing specific values, having specific types... In the example below we're omitting parts of action and state objects with the key `data` (in case of action only when was dispatched action `FILE_DOWNLOAD_SUCCESS`):
|
You can do that by including/omitting data containing specific values, having specific types... In the example below we're omitting parts of action and state objects with the key `data` (in case of action only when was dispatched action `FILE_DOWNLOAD_SUCCESS`):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const actionSanitizer = (action) => (
|
const actionSanitizer = (action) =>
|
||||||
action.type === 'FILE_DOWNLOAD_SUCCESS' && action.data ?
|
action.type === 'FILE_DOWNLOAD_SUCCESS' && action.data
|
||||||
{ ...action, data: '<<LONG_BLOB>>' } : action
|
? { ...action, data: '<<LONG_BLOB>>' }
|
||||||
);
|
: action;
|
||||||
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = createStore(
|
||||||
|
rootReducer,
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
actionSanitizer,
|
actionSanitizer,
|
||||||
stateSanitizer: (state) => state.data ? { ...state, data: '<<LONG_BLOB>>' } : state
|
stateSanitizer: (state) =>
|
||||||
}));
|
state.data ? { ...state, data: '<<LONG_BLOB>>' } : state,
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
There's a more advanced [example on how to implement that for `ui-router`](https://github.com/zalmoxisus/redux-devtools-extension/issues/455#issuecomment-404538385).
|
There's a more advanced [example on how to implement that for `ui-router`](https://github.com/zalmoxisus/redux-devtools-extension/issues/455#issuecomment-404538385).
|
||||||
|
@ -60,6 +71,7 @@ The extension is in different process and cannot access the store object directl
|
||||||
React synthetic event cannot be reused for performance reason. So, it's not possible to serialize event objects you pass to action payloads.
|
React synthetic event cannot be reused for performance reason. So, it's not possible to serialize event objects you pass to action payloads.
|
||||||
|
|
||||||
1. The best solution is **not to pass the whole event object to reducers, but the data you need**:
|
1. The best solution is **not to pass the whole event object to reducers, but the data you need**:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
function click(event) {
|
function click(event) {
|
||||||
return {
|
return {
|
||||||
|
@ -95,6 +107,7 @@ React synthetic event cannot be reused for performance reason. So, it's not poss
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that it shouldn't be arrow function as we want to have access to the function's `this`.
|
Note that it shouldn't be arrow function as we want to have access to the function's `this`.
|
||||||
|
|
||||||
As we don't have access to the original object, skipping and recomputing actions during hot reloading will not work in this case. We recommend to use the first solution whenever possible.
|
As we don't have access to the original object, skipping and recomputing actions during hot reloading will not work in this case. We recommend to use the first solution whenever possible.
|
||||||
|
@ -102,10 +115,15 @@ React synthetic event cannot be reused for performance reason. So, it's not poss
|
||||||
### Symbols or other unserializable data not shown
|
### Symbols or other unserializable data not shown
|
||||||
|
|
||||||
To get data which cannot be serialized by `JSON.stringify`, set [`serialize` parameter](/docs/API/Arguments.md#serialize):
|
To get data which cannot be serialized by `JSON.stringify`, set [`serialize` parameter](/docs/API/Arguments.md#serialize):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const store = Redux.createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__({
|
const store = Redux.createStore(
|
||||||
serialize: true
|
reducer,
|
||||||
}));
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
|
serialize: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
It will handle also date, regex, undefined, error objects, symbols, maps, sets and functions.
|
It will handle also date, regex, undefined, error objects, symbols, maps, sets and functions.
|
||||||
|
|
|
@ -13,7 +13,7 @@ var exampleDirs = fs.readdirSync(__dirname).filter((file) => {
|
||||||
// Ordering is important here. `npm install` must come first.
|
// Ordering is important here. `npm install` must come first.
|
||||||
var cmdArgs = [
|
var cmdArgs = [
|
||||||
{ cmd: 'npm', args: ['install'] },
|
{ cmd: 'npm', args: ['install'] },
|
||||||
{ cmd: 'webpack', args: ['index.js'] }
|
{ cmd: 'webpack', args: ['index.js'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const dir of exampleDirs) {
|
for (const dir of exampleDirs) {
|
||||||
|
@ -21,7 +21,7 @@ for (const dir of exampleDirs) {
|
||||||
// declare opts in this scope to avoid https://github.com/joyent/node/issues/9158
|
// declare opts in this scope to avoid https://github.com/joyent/node/issues/9158
|
||||||
const opts = {
|
const opts = {
|
||||||
cwd: path.join(__dirname, dir),
|
cwd: path.join(__dirname, dir),
|
||||||
stdio: 'inherit'
|
stdio: 'inherit',
|
||||||
};
|
};
|
||||||
let result = {};
|
let result = {};
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|
|
@ -5,18 +5,18 @@ let t;
|
||||||
|
|
||||||
export function increment() {
|
export function increment() {
|
||||||
return {
|
return {
|
||||||
type: INCREMENT_COUNTER
|
type: INCREMENT_COUNTER,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decrement() {
|
export function decrement() {
|
||||||
return {
|
return {
|
||||||
type: DECREMENT_COUNTER
|
type: DECREMENT_COUNTER,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function autoIncrement(delay = 10) {
|
export function autoIncrement(delay = 10) {
|
||||||
return dispatch => {
|
return (dispatch) => {
|
||||||
if (t) {
|
if (t) {
|
||||||
clearInterval(t);
|
clearInterval(t);
|
||||||
t = undefined;
|
t = undefined;
|
||||||
|
@ -29,7 +29,7 @@ export function autoIncrement(delay = 10) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function incrementAsync(delay = 1000) {
|
export function incrementAsync(delay = 1000) {
|
||||||
return dispatch => {
|
return (dispatch) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
dispatch(increment());
|
dispatch(increment());
|
||||||
}, delay);
|
}, delay);
|
||||||
|
|
|
@ -3,17 +3,18 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { increment, autoIncrement, incrementAsync, decrement, counter } = this.props;
|
const {
|
||||||
|
increment,
|
||||||
|
autoIncrement,
|
||||||
|
incrementAsync,
|
||||||
|
decrement,
|
||||||
|
counter,
|
||||||
|
} = this.props;
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Clicked: {counter} times
|
Clicked: {counter} times <button onClick={increment}>+</button>{' '}
|
||||||
{' '}
|
<button onClick={decrement}>-</button>{' '}
|
||||||
<button onClick={increment}>+</button>
|
<button onClick={incrementAsync}>Increment async</button>{' '}
|
||||||
{' '}
|
|
||||||
<button onClick={decrement}>-</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={incrementAsync}>Increment async</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={autoIncrement}>Auto increment</button>
|
<button onClick={autoIncrement}>Auto increment</button>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
@ -25,7 +26,7 @@ Counter.propTypes = {
|
||||||
autoIncrement: PropTypes.func.isRequired,
|
autoIncrement: PropTypes.func.isRequired,
|
||||||
incrementAsync: PropTypes.func.isRequired,
|
incrementAsync: PropTypes.func.isRequired,
|
||||||
decrement: PropTypes.func.isRequired,
|
decrement: PropTypes.func.isRequired,
|
||||||
counter: PropTypes.number.isRequired
|
counter: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Counter;
|
export default Counter;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as CounterActions from '../actions/counter';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
counter: state.counter
|
counter: state.counter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<title>Redux counter example</title>
|
<title>Redux counter example</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">
|
<div id="root"></div>
|
||||||
</div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
<script src="/static/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { combineReducers } from 'redux';
|
||||||
import counter from './counter';
|
import counter from './counter';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
counter
|
counter,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -7,10 +7,15 @@ var app = new require('express')();
|
||||||
var port = 4001;
|
var port = 4001;
|
||||||
|
|
||||||
var compiler = webpack(config);
|
var compiler = webpack(config);
|
||||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
})
|
||||||
|
);
|
||||||
app.use(webpackHotMiddleware(compiler));
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
app.get("/", function(req, res) {
|
app.get('/', function (req, res) {
|
||||||
res.sendFile(__dirname + '/index.html');
|
res.sendFile(__dirname + '/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,6 +23,10 @@ app.listen(port, function(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port);
|
console.info(
|
||||||
|
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
||||||
|
port,
|
||||||
|
port
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,15 +6,21 @@ import reducer from '../reducers';
|
||||||
import * as actionCreators from '../actions/counter';
|
import * as actionCreators from '../actions/counter';
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
export default function configureStore(preloadedState) {
|
||||||
const composeEnhancers = composeWithDevTools({ actionCreators, trace: true, traceLimit: 25 });
|
const composeEnhancers = composeWithDevTools({
|
||||||
const store = createStore(reducer, preloadedState, composeEnhancers(
|
actionCreators,
|
||||||
applyMiddleware(invariant(), thunk)
|
trace: true,
|
||||||
));
|
traceLimit: 25,
|
||||||
|
});
|
||||||
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
preloadedState,
|
||||||
|
composeEnhancers(applyMiddleware(invariant(), thunk))
|
||||||
|
);
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
// Enable Webpack hot module replacement for reducers
|
// Enable Webpack hot module replacement for reducers
|
||||||
module.hot.accept('../reducers', () => {
|
module.hot.accept('../reducers', () => {
|
||||||
store.replaceReducer(require('../reducers').default)
|
store.replaceReducer(require('../reducers').default);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,17 @@ function mockStore(getState, expectedActions, onLastAction) {
|
||||||
if (!Array.isArray(expectedActions)) {
|
if (!Array.isArray(expectedActions)) {
|
||||||
throw new Error('expectedActions should be an array of expected actions.');
|
throw new Error('expectedActions should be an array of expected actions.');
|
||||||
}
|
}
|
||||||
if (typeof onLastAction !== 'undefined' && typeof onLastAction !== 'function') {
|
if (
|
||||||
|
typeof onLastAction !== 'undefined' &&
|
||||||
|
typeof onLastAction !== 'function'
|
||||||
|
) {
|
||||||
throw new Error('onLastAction should either be undefined or function.');
|
throw new Error('onLastAction should either be undefined or function.');
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockStoreWithoutMiddleware() {
|
function mockStoreWithoutMiddleware() {
|
||||||
return {
|
return {
|
||||||
getState() {
|
getState() {
|
||||||
return typeof getState === 'function' ?
|
return typeof getState === 'function' ? getState() : getState;
|
||||||
getState() :
|
|
||||||
getState;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dispatch(action) {
|
dispatch(action) {
|
||||||
|
@ -31,13 +32,13 @@ function mockStore(getState, expectedActions, onLastAction) {
|
||||||
onLastAction();
|
onLastAction();
|
||||||
}
|
}
|
||||||
return action;
|
return action;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockStoreWithMiddleware = applyMiddleware(
|
const mockStoreWithMiddleware = applyMiddleware(...middlewares)(
|
||||||
...middlewares
|
mockStoreWithoutMiddleware
|
||||||
)(mockStoreWithoutMiddleware);
|
);
|
||||||
|
|
||||||
return mockStoreWithMiddleware();
|
return mockStoreWithMiddleware();
|
||||||
}
|
}
|
||||||
|
@ -52,9 +53,7 @@ describe('actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('incrementIfOdd should create increment action', (done) => {
|
it('incrementIfOdd should create increment action', (done) => {
|
||||||
const expectedActions = [
|
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
||||||
{ type: actions.INCREMENT_COUNTER }
|
|
||||||
];
|
|
||||||
const store = mockStore({ counter: 1 }, expectedActions, done);
|
const store = mockStore({ counter: 1 }, expectedActions, done);
|
||||||
store.dispatch(actions.incrementIfOdd());
|
store.dispatch(actions.incrementIfOdd());
|
||||||
});
|
});
|
||||||
|
@ -67,9 +66,7 @@ describe('actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('incrementAsync should create increment action', (done) => {
|
it('incrementAsync should create increment action', (done) => {
|
||||||
const expectedActions = [
|
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
||||||
{ type: actions.INCREMENT_COUNTER }
|
|
||||||
];
|
|
||||||
const store = mockStore({ counter: 0 }, expectedActions, done);
|
const store = mockStore({ counter: 0 }, expectedActions, done);
|
||||||
store.dispatch(actions.incrementAsync(100));
|
store.dispatch(actions.incrementAsync(100));
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,14 +8,16 @@ function setup() {
|
||||||
increment: expect.createSpy(),
|
increment: expect.createSpy(),
|
||||||
incrementIfOdd: expect.createSpy(),
|
incrementIfOdd: expect.createSpy(),
|
||||||
incrementAsync: expect.createSpy(),
|
incrementAsync: expect.createSpy(),
|
||||||
decrement: expect.createSpy()
|
decrement: expect.createSpy(),
|
||||||
};
|
};
|
||||||
const component = TestUtils.renderIntoDocument(<Counter counter={1} {...actions} />);
|
const component = TestUtils.renderIntoDocument(
|
||||||
|
<Counter counter={1} {...actions} />
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
component: component,
|
component: component,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
buttons: TestUtils.scryRenderedDOMComponentsWithTag(component, 'button'),
|
buttons: TestUtils.scryRenderedDOMComponentsWithTag(component, 'button'),
|
||||||
p: TestUtils.findRenderedDOMComponentWithTag(component, 'p')
|
p: TestUtils.findRenderedDOMComponentWithTag(component, 'p'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ function setup(initialState) {
|
||||||
return {
|
return {
|
||||||
app: app,
|
app: app,
|
||||||
buttons: TestUtils.scryRenderedDOMComponentsWithTag(app, 'button'),
|
buttons: TestUtils.scryRenderedDOMComponentsWithTag(app, 'button'),
|
||||||
p: TestUtils.findRenderedDOMComponentWithTag(app, 'p')
|
p: TestUtils.findRenderedDOMComponentWithTag(app, 'p'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,23 +4,20 @@ var webpack = require('webpack');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: [
|
entry: ['webpack-hot-middleware/client', './index'],
|
||||||
'webpack-hot-middleware/client',
|
|
||||||
'./index'
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
publicPath: '/static/'
|
publicPath: '/static/',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||||
new webpack.HotModuleReplacementPlugin()
|
|
||||||
],
|
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loaders: ['babel-loader'],
|
loaders: ['babel-loader'],
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/,
|
||||||
}]
|
},
|
||||||
}
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
const withDevTools = (
|
const withDevTools =
|
||||||
// process.env.NODE_ENV === 'development' &&
|
// process.env.NODE_ENV === 'development' &&
|
||||||
typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__
|
typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__;
|
||||||
);
|
|
||||||
|
|
||||||
class Counter extends Component {
|
class Counter extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -20,7 +19,10 @@ class Counter extends Component {
|
||||||
this.unsubscribe = this.devTools.subscribe((message) => {
|
this.unsubscribe = this.devTools.subscribe((message) => {
|
||||||
// Implement monitors actions.
|
// Implement monitors actions.
|
||||||
// For example time traveling:
|
// For example time traveling:
|
||||||
if (message.type === 'DISPATCH' && message.payload.type === 'JUMP_TO_STATE') {
|
if (
|
||||||
|
message.type === 'DISPATCH' &&
|
||||||
|
message.payload.type === 'JUMP_TO_STATE'
|
||||||
|
) {
|
||||||
this.setState(message.state);
|
this.setState(message.state);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -50,10 +52,7 @@ class Counter extends Component {
|
||||||
const { counter } = this.state;
|
const { counter } = this.state;
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
Clicked: {counter} times
|
Clicked: {counter} times <button onClick={this.increment}>+</button>{' '}
|
||||||
{' '}
|
|
||||||
<button onClick={this.increment}>+</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={this.decrement}>-</button>
|
<button onClick={this.decrement}>-</button>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<title>React counter example</title>
|
<title>React counter example</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">
|
<div id="root"></div>
|
||||||
</div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
<script src="/static/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,7 +2,4 @@ import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import Counter from './components/Counter';
|
import Counter from './components/Counter';
|
||||||
|
|
||||||
render(
|
render(<Counter />, document.getElementById('root'));
|
||||||
<Counter />,
|
|
||||||
document.getElementById('root')
|
|
||||||
);
|
|
||||||
|
|
|
@ -4,22 +4,22 @@ var webpack = require('webpack');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: [
|
entry: ['./index'],
|
||||||
'./index'
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
publicPath: '/static/'
|
publicPath: '/static/',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loaders: ['babel-loader'],
|
loaders: ['babel-loader'],
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/,
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 4004
|
port: 4004,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import React, { PropTypes, Component } from 'react';
|
import React, { PropTypes, Component } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
const FILTER_TITLES = {
|
const FILTER_TITLES = {
|
||||||
[SHOW_ALL]: 'All',
|
[SHOW_ALL]: 'All',
|
||||||
[SHOW_ACTIVE]: 'Active',
|
[SHOW_ACTIVE]: 'Active',
|
||||||
[SHOW_COMPLETED]: 'Completed'
|
[SHOW_COMPLETED]: 'Completed',
|
||||||
};
|
};
|
||||||
|
|
||||||
class Footer extends Component {
|
class Footer extends Component {
|
||||||
|
@ -25,9 +29,11 @@ class Footer extends Component {
|
||||||
const { filter: selectedFilter, onShow } = this.props;
|
const { filter: selectedFilter, onShow } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className={classnames({ selected: filter === selectedFilter })}
|
<a
|
||||||
|
className={classnames({ selected: filter === selectedFilter })}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => onShow(filter)}>
|
onClick={() => onShow(filter)}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -37,8 +43,7 @@ class Footer extends Component {
|
||||||
const { completedCount, onClearCompleted } = this.props;
|
const { completedCount, onClearCompleted } = this.props;
|
||||||
if (completedCount > 0) {
|
if (completedCount > 0) {
|
||||||
return (
|
return (
|
||||||
<button className="clear-completed"
|
<button className="clear-completed" onClick={onClearCompleted}>
|
||||||
onClick={onClearCompleted} >
|
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -50,11 +55,9 @@ class Footer extends Component {
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
{this.renderTodoCount()}
|
{this.renderTodoCount()}
|
||||||
<ul className="filters">
|
<ul className="filters">
|
||||||
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
|
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
||||||
<li key={filter}>
|
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
||||||
{this.renderFilterLink(filter)}
|
))}
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
{this.renderClearButton()}
|
{this.renderClearButton()}
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -67,7 +70,7 @@ Footer.propTypes = {
|
||||||
activeCount: PropTypes.number.isRequired,
|
activeCount: PropTypes.number.isRequired,
|
||||||
filter: PropTypes.string.isRequired,
|
filter: PropTypes.string.isRequired,
|
||||||
onClearCompleted: PropTypes.func.isRequired,
|
onClearCompleted: PropTypes.func.isRequired,
|
||||||
onShow: PropTypes.func.isRequired
|
onShow: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|
|
@ -13,16 +13,18 @@ class Header extends Component {
|
||||||
return (
|
return (
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<h1 style={{ fontSize: 80 }}>{path}</h1>
|
<h1 style={{ fontSize: 80 }}>{path}</h1>
|
||||||
<TodoTextInput newTodo
|
<TodoTextInput
|
||||||
|
newTodo
|
||||||
onSave={this.handleSave.bind(this)}
|
onSave={this.handleSave.bind(this)}
|
||||||
placeholder="What needs to be done?" />
|
placeholder="What needs to be done?"
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
addTodo: PropTypes.func.isRequired
|
addTodo: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import TodoItem from './TodoItem';
|
import TodoItem from './TodoItem';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
const TODO_FILTERS = {
|
const TODO_FILTERS = {
|
||||||
[SHOW_ALL]: () => true,
|
[SHOW_ALL]: () => true,
|
||||||
[SHOW_ACTIVE]: todo => !todo.completed,
|
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
||||||
[SHOW_COMPLETED]: todo => todo.completed
|
[SHOW_COMPLETED]: (todo) => todo.completed,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MainSection extends Component {
|
class MainSection extends Component {
|
||||||
|
@ -16,7 +20,7 @@ class MainSection extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClearCompleted() {
|
handleClearCompleted() {
|
||||||
const atLeastOneCompleted = this.props.todos.some(todo => todo.completed);
|
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
||||||
if (atLeastOneCompleted) {
|
if (atLeastOneCompleted) {
|
||||||
this.props.actions.clearCompleted();
|
this.props.actions.clearCompleted();
|
||||||
}
|
}
|
||||||
|
@ -30,10 +34,12 @@ class MainSection extends Component {
|
||||||
const { todos, actions } = this.props;
|
const { todos, actions } = this.props;
|
||||||
if (todos.length > 0) {
|
if (todos.length > 0) {
|
||||||
return (
|
return (
|
||||||
<input className="toggle-all"
|
<input
|
||||||
|
className="toggle-all"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={completedCount === todos.length}
|
checked={completedCount === todos.length}
|
||||||
onChange={actions.completeAll} />
|
onChange={actions.completeAll}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +51,13 @@ class MainSection extends Component {
|
||||||
|
|
||||||
if (todos.length) {
|
if (todos.length) {
|
||||||
return (
|
return (
|
||||||
<Footer completedCount={completedCount}
|
<Footer
|
||||||
|
completedCount={completedCount}
|
||||||
activeCount={activeCount}
|
activeCount={activeCount}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onClearCompleted={this.handleClearCompleted.bind(this)}
|
onClearCompleted={this.handleClearCompleted.bind(this)}
|
||||||
onShow={this.handleShow.bind(this)} />
|
onShow={this.handleShow.bind(this)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,8 +67,8 @@ class MainSection extends Component {
|
||||||
const { filter } = this.state;
|
const { filter } = this.state;
|
||||||
|
|
||||||
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
||||||
const completedCount = todos.reduce((count, todo) =>
|
const completedCount = todos.reduce(
|
||||||
todo.completed ? count + 1 : count,
|
(count, todo) => (todo.completed ? count + 1 : count),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -68,9 +76,9 @@ class MainSection extends Component {
|
||||||
<section className="main">
|
<section className="main">
|
||||||
{this.renderToggleAll(completedCount)}
|
{this.renderToggleAll(completedCount)}
|
||||||
<ul className="todo-list">
|
<ul className="todo-list">
|
||||||
{filteredTodos.map(todo =>
|
{filteredTodos.map((todo) => (
|
||||||
<TodoItem key={todo.id} todo={todo} {...actions} />
|
<TodoItem key={todo.id} todo={todo} {...actions} />
|
||||||
)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{this.renderFooter(completedCount)}
|
{this.renderFooter(completedCount)}
|
||||||
</section>
|
</section>
|
||||||
|
@ -80,7 +88,7 @@ class MainSection extends Component {
|
||||||
|
|
||||||
MainSection.propTypes = {
|
MainSection.propTypes = {
|
||||||
todos: PropTypes.array.isRequired,
|
todos: PropTypes.array.isRequired,
|
||||||
actions: PropTypes.object.isRequired
|
actions: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MainSection;
|
export default MainSection;
|
||||||
|
|
|
@ -6,7 +6,7 @@ class TodoItem extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
editing: false
|
editing: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,31 +29,36 @@ class TodoItem extends Component {
|
||||||
let element;
|
let element;
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
element = (
|
element = (
|
||||||
<TodoTextInput text={todo.text}
|
<TodoTextInput
|
||||||
|
text={todo.text}
|
||||||
editing={this.state.editing}
|
editing={this.state.editing}
|
||||||
onSave={(text) => this.handleSave(todo.id, text)} />
|
onSave={(text) => this.handleSave(todo.id, text)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
element = (
|
element = (
|
||||||
<div className="view">
|
<div className="view">
|
||||||
<input className="toggle"
|
<input
|
||||||
|
className="toggle"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={todo.completed}
|
checked={todo.completed}
|
||||||
onChange={() => completeTodo(todo.id)} />
|
onChange={() => completeTodo(todo.id)}
|
||||||
|
/>
|
||||||
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
||||||
{todo.text}
|
{todo.text}
|
||||||
</label>
|
</label>
|
||||||
<button className="destroy"
|
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
||||||
onClick={() => deleteTodo(todo.id)} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={classnames({
|
<li
|
||||||
|
className={classnames({
|
||||||
completed: todo.completed,
|
completed: todo.completed,
|
||||||
editing: this.state.editing
|
editing: this.state.editing,
|
||||||
})}>
|
})}
|
||||||
|
>
|
||||||
{element}
|
{element}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -64,7 +69,7 @@ TodoItem.propTypes = {
|
||||||
todo: PropTypes.object.isRequired,
|
todo: PropTypes.object.isRequired,
|
||||||
editTodo: PropTypes.func.isRequired,
|
editTodo: PropTypes.func.isRequired,
|
||||||
deleteTodo: PropTypes.func.isRequired,
|
deleteTodo: PropTypes.func.isRequired,
|
||||||
completeTodo: PropTypes.func.isRequired
|
completeTodo: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TodoItem;
|
export default TodoItem;
|
||||||
|
|
|
@ -5,7 +5,7 @@ class TodoTextInput extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
text: this.props.text || ''
|
text: this.props.text || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ class TodoTextInput extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<input className={
|
<input
|
||||||
classnames({
|
className={classnames({
|
||||||
edit: this.props.editing,
|
edit: this.props.editing,
|
||||||
'new-todo': this.props.newTodo
|
'new-todo': this.props.newTodo,
|
||||||
})}
|
})}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
|
@ -42,7 +42,8 @@ class TodoTextInput extends Component {
|
||||||
value={this.state.text}
|
value={this.state.text}
|
||||||
onBlur={this.handleBlur.bind(this)}
|
onBlur={this.handleBlur.bind(this)}
|
||||||
onChange={this.handleChange.bind(this)}
|
onChange={this.handleChange.bind(this)}
|
||||||
onKeyDown={this.handleSubmit.bind(this)} />
|
onKeyDown={this.handleSubmit.bind(this)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ TodoTextInput.propTypes = {
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
newTodo: PropTypes.bool
|
newTodo: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TodoTextInput;
|
export default TodoTextInput;
|
||||||
|
|
|
@ -19,23 +19,20 @@ class App extends Component {
|
||||||
|
|
||||||
App.propTypes = {
|
App.propTypes = {
|
||||||
todos: PropTypes.array.isRequired,
|
todos: PropTypes.array.isRequired,
|
||||||
actions: PropTypes.object.isRequired
|
actions: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
todos: state.todos,
|
todos: state.todos,
|
||||||
path: state.router.location.pathname
|
path: state.router.location.pathname,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(TodoActions, dispatch)
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(App);
|
|
||||||
|
|
|
@ -8,14 +8,14 @@ import * as TodoActions from '../actions/todos';
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
pushState: bindActionCreators(pushState, dispatch),
|
pushState: bindActionCreators(pushState, dispatch),
|
||||||
actions: bindActionCreators(TodoActions, dispatch)
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect((state) => ({}), mapDispatchToProps)
|
@connect((state) => ({}), mapDispatchToProps)
|
||||||
class Wrapper extends Component {
|
class Wrapper extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -41,11 +41,23 @@ class Wrapper extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ padding: 20, backgroundColor: '#eee', fontWeight: 'bold', textAlign: 'center' }}>
|
<div
|
||||||
<a href="#" onClick={this.handleClick}>Standard Todo</a> | <a href="#" onClick={this.handleClick}>AutoTodo</a>
|
style={{
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: '#eee',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a href="#" onClick={this.handleClick}>
|
||||||
|
Standard Todo
|
||||||
|
</a>{' '}
|
||||||
|
|{' '}
|
||||||
|
<a href="#" onClick={this.handleClick}>
|
||||||
|
AutoTodo
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<title>Redux TodoMVC example</title>
|
<title>Redux TodoMVC example</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="todoapp" id="root">
|
<div class="todoapp" id="root"></div>
|
||||||
</div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
<script src="/static/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import todos from './todos';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
todos,
|
todos,
|
||||||
router: routerStateReducer
|
router: routerStateReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -1,47 +1,59 @@
|
||||||
import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes';
|
import {
|
||||||
|
ADD_TODO,
|
||||||
|
DELETE_TODO,
|
||||||
|
EDIT_TODO,
|
||||||
|
COMPLETE_TODO,
|
||||||
|
COMPLETE_ALL,
|
||||||
|
CLEAR_COMPLETED,
|
||||||
|
} from '../constants/ActionTypes';
|
||||||
|
|
||||||
const initialState = [{
|
const initialState = [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function todos(state = initialState, action) {
|
export default function todos(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ADD_TODO:
|
case ADD_TODO:
|
||||||
return [{
|
return [
|
||||||
|
{
|
||||||
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
||||||
completed: false,
|
completed: false,
|
||||||
text: action.text
|
text: action.text,
|
||||||
}, ...state];
|
},
|
||||||
|
...state,
|
||||||
|
];
|
||||||
|
|
||||||
case DELETE_TODO:
|
case DELETE_TODO:
|
||||||
return state.filter(todo =>
|
return state.filter((todo) => todo.id !== action.id);
|
||||||
todo.id !== action.id
|
|
||||||
);
|
|
||||||
|
|
||||||
case EDIT_TODO:
|
case EDIT_TODO:
|
||||||
return state.map(todo =>
|
return state.map((todo) =>
|
||||||
todo.id === action.id ?
|
todo.id === action.id
|
||||||
Object.assign({}, todo, { text: action.text }) :
|
? Object.assign({}, todo, { text: action.text })
|
||||||
todo
|
: todo
|
||||||
);
|
);
|
||||||
|
|
||||||
case COMPLETE_TODO:
|
case COMPLETE_TODO:
|
||||||
return state.map(todo =>
|
return state.map((todo) =>
|
||||||
todo.id === action.id ?
|
todo.id === action.id
|
||||||
Object.assign({}, todo, { completed: !todo.completed }) :
|
? Object.assign({}, todo, { completed: !todo.completed })
|
||||||
todo
|
: todo
|
||||||
);
|
);
|
||||||
|
|
||||||
case COMPLETE_ALL:
|
case COMPLETE_ALL:
|
||||||
const areAllMarked = state.every(todo => todo.completed);
|
const areAllMarked = state.every((todo) => todo.completed);
|
||||||
return state.map(todo => Object.assign({}, todo, {
|
return state.map((todo) =>
|
||||||
completed: !areAllMarked
|
Object.assign({}, todo, {
|
||||||
}));
|
completed: !areAllMarked,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
case CLEAR_COMPLETED:
|
case CLEAR_COMPLETED:
|
||||||
return state.filter(todo => todo.completed === false);
|
return state.filter((todo) => todo.completed === false);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -7,10 +7,15 @@ var app = new require('express')();
|
||||||
var port = 4002;
|
var port = 4002;
|
||||||
|
|
||||||
var compiler = webpack(config);
|
var compiler = webpack(config);
|
||||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
})
|
||||||
|
);
|
||||||
app.use(webpackHotMiddleware(compiler));
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
app.get("/", function(req, res) {
|
app.get('/', function (req, res) {
|
||||||
res.sendFile(__dirname + '/index.html');
|
res.sendFile(__dirname + '/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,6 +23,10 @@ app.listen(port, function(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port);
|
console.info(
|
||||||
|
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
||||||
|
port,
|
||||||
|
port
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { createStore, compose } from 'redux';
|
import { createStore, compose } from 'redux';
|
||||||
import { reduxReactRouter, routerStateReducer, ReduxRouter } from 'redux-router';
|
import {
|
||||||
|
reduxReactRouter,
|
||||||
|
routerStateReducer,
|
||||||
|
ReduxRouter,
|
||||||
|
} from 'redux-router';
|
||||||
//import createHistory from 'history/lib/createBrowserHistory';
|
//import createHistory from 'history/lib/createBrowserHistory';
|
||||||
import createHistory from 'history/lib/createHashHistory';
|
import createHistory from 'history/lib/createHashHistory';
|
||||||
import rootReducer from '../reducers';
|
import rootReducer from '../reducers';
|
||||||
|
@ -7,7 +11,7 @@ import rootReducer from '../reducers';
|
||||||
export default function configureStore(initialState) {
|
export default function configureStore(initialState) {
|
||||||
let finalCreateStore = compose(
|
let finalCreateStore = compose(
|
||||||
reduxReactRouter({ createHistory }),
|
reduxReactRouter({ createHistory }),
|
||||||
global.devToolsExtension ? global.devToolsExtension() : f => f
|
global.devToolsExtension ? global.devToolsExtension() : (f) => f
|
||||||
)(createStore);
|
)(createStore);
|
||||||
|
|
||||||
const store = finalCreateStore(rootReducer, initialState);
|
const store = finalCreateStore(rootReducer, initialState);
|
||||||
|
|
|
@ -6,14 +6,14 @@ describe('todo actions', () => {
|
||||||
it('addTodo should create ADD_TODO action', () => {
|
it('addTodo should create ADD_TODO action', () => {
|
||||||
expect(actions.addTodo('Use Redux')).toEqual({
|
expect(actions.addTodo('Use Redux')).toEqual({
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Use Redux'
|
text: 'Use Redux',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deleteTodo should create DELETE_TODO action', () => {
|
it('deleteTodo should create DELETE_TODO action', () => {
|
||||||
expect(actions.deleteTodo(1)).toEqual({
|
expect(actions.deleteTodo(1)).toEqual({
|
||||||
type: types.DELETE_TODO,
|
type: types.DELETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,26 +21,26 @@ describe('todo actions', () => {
|
||||||
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
||||||
type: types.EDIT_TODO,
|
type: types.EDIT_TODO,
|
||||||
id: 1,
|
id: 1,
|
||||||
text: 'Use Redux everywhere'
|
text: 'Use Redux everywhere',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completeTodo should create COMPLETE_TODO action', () => {
|
it('completeTodo should create COMPLETE_TODO action', () => {
|
||||||
expect(actions.completeTodo(1)).toEqual({
|
expect(actions.completeTodo(1)).toEqual({
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completeAll should create COMPLETE_ALL action', () => {
|
it('completeAll should create COMPLETE_ALL action', () => {
|
||||||
expect(actions.completeAll()).toEqual({
|
expect(actions.completeAll()).toEqual({
|
||||||
type: types.COMPLETE_ALL
|
type: types.COMPLETE_ALL,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
||||||
expect(actions.clearCompleted('Use Redux')).toEqual({
|
expect(actions.clearCompleted('Use Redux')).toEqual({
|
||||||
type: types.CLEAR_COMPLETED
|
type: types.CLEAR_COMPLETED,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,13 +5,16 @@ import Footer from '../../components/Footer';
|
||||||
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
|
{
|
||||||
completedCount: 0,
|
completedCount: 0,
|
||||||
activeCount: 0,
|
activeCount: 0,
|
||||||
filter: SHOW_ALL,
|
filter: SHOW_ALL,
|
||||||
onClearCompleted: expect.createSpy(),
|
onClearCompleted: expect.createSpy(),
|
||||||
onShow: expect.createSpy()
|
onShow: expect.createSpy(),
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
renderer.render(<Footer {...props} />);
|
renderer.render(<Footer {...props} />);
|
||||||
|
@ -19,13 +22,14 @@ function setup(propOverrides) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output
|
output: output,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextContent(elem) {
|
function getTextContent(elem) {
|
||||||
const children = Array.isArray(elem.props.children) ?
|
const children = Array.isArray(elem.props.children)
|
||||||
elem.props.children : [elem.props.children];
|
? elem.props.children
|
||||||
|
: [elem.props.children];
|
||||||
|
|
||||||
return children.reduce(function concatText(out, child) {
|
return children.reduce(function concatText(out, child) {
|
||||||
// Children are either elements or text strings
|
// Children are either elements or text strings
|
||||||
|
@ -63,11 +67,13 @@ describe('components', () => {
|
||||||
expect(filter.type).toBe('li');
|
expect(filter.type).toBe('li');
|
||||||
const a = filter.props.children;
|
const a = filter.props.children;
|
||||||
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
||||||
expect(a.props.children).toBe({
|
expect(a.props.children).toBe(
|
||||||
|
{
|
||||||
0: 'All',
|
0: 'All',
|
||||||
1: 'Active',
|
1: 'Active',
|
||||||
2: 'Completed'
|
2: 'Completed',
|
||||||
}[i]);
|
}[i]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const props = {
|
const props = {
|
||||||
addTodo: expect.createSpy()
|
addTodo: expect.createSpy(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
@ -16,7 +16,7 @@ function setup() {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,24 +7,30 @@ import Footer from '../../components/Footer';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
todos: [{
|
{
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
actions: {
|
actions: {
|
||||||
editTodo: expect.createSpy(),
|
editTodo: expect.createSpy(),
|
||||||
deleteTodo: expect.createSpy(),
|
deleteTodo: expect.createSpy(),
|
||||||
completeTodo: expect.createSpy(),
|
completeTodo: expect.createSpy(),
|
||||||
completeAll: expect.createSpy(),
|
completeAll: expect.createSpy(),
|
||||||
clearCompleted: expect.createSpy()
|
clearCompleted: expect.createSpy(),
|
||||||
}
|
},
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
renderer.render(<MainSection {...props} />);
|
renderer.render(<MainSection {...props} />);
|
||||||
|
@ -33,7 +39,7 @@ function setup(propOverrides) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +61,15 @@ describe('components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be checked if all todos completed', () => {
|
it('should be checked if all todos completed', () => {
|
||||||
const { output } = setup({ todos: [{
|
const { output } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}]});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
const [toggle] = output.props.children;
|
const [toggle] = output.props.children;
|
||||||
expect(toggle.props.checked).toBe(true);
|
expect(toggle.props.checked).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -99,11 +109,15 @@ describe('components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
||||||
const { output, props } = setup({ todos: [{
|
const { output, props } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
const [, , footer] = output.props.children;
|
const [, , footer] = output.props.children;
|
||||||
footer.props.onClearCompleted();
|
footer.props.onClearCompleted();
|
||||||
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
||||||
|
|
|
@ -9,18 +9,16 @@ function setup( editing = false ) {
|
||||||
todo: {
|
todo: {
|
||||||
id: 0,
|
id: 0,
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false
|
completed: false,
|
||||||
},
|
},
|
||||||
editTodo: expect.createSpy(),
|
editTodo: expect.createSpy(),
|
||||||
deleteTodo: expect.createSpy(),
|
deleteTodo: expect.createSpy(),
|
||||||
completeTodo: expect.createSpy()
|
completeTodo: expect.createSpy(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
renderer.render(
|
renderer.render(<TodoItem {...props} />);
|
||||||
<TodoItem {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
@ -33,7 +31,7 @@ function setup( editing = false ) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,20 @@ import TestUtils from 'react-addons-test-utils';
|
||||||
import TodoTextInput from '../../components/TodoTextInput';
|
import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
|
{
|
||||||
onSave: expect.createSpy(),
|
onSave: expect.createSpy(),
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
placeholder: 'What needs to be done?',
|
placeholder: 'What needs to be done?',
|
||||||
editing: false,
|
editing: false,
|
||||||
newTodo: false
|
newTodo: false,
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
renderer.render(
|
renderer.render(<TodoTextInput {...props} />);
|
||||||
<TodoTextInput {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ function setup(propOverrides) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,240 +4,322 @@ import * as types from '../../constants/ActionTypes';
|
||||||
|
|
||||||
describe('todos reducer', () => {
|
describe('todos reducer', () => {
|
||||||
it('should handle initial state', () => {
|
it('should handle initial state', () => {
|
||||||
expect(
|
expect(todos(undefined, {})).toEqual([
|
||||||
todos(undefined, {})
|
{
|
||||||
).toEqual([{
|
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle ADD_TODO', () => {
|
it('should handle ADD_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([], {
|
todos([], {
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Run the tests'
|
text: 'Run the tests',
|
||||||
})
|
})
|
||||||
).toEqual([{
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Run the tests'
|
text: 'Run the tests',
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Fix the tests'
|
text: 'Fix the tests',
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 2
|
id: 2,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle DELETE_TODO', () => {
|
it('should handle DELETE_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.DELETE_TODO,
|
type: types.DELETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle EDIT_TODO', () => {
|
it('should handle EDIT_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.EDIT_TODO,
|
type: types.EDIT_TODO,
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle COMPLETE_TODO', () => {
|
it('should handle COMPLETE_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle COMPLETE_ALL', () => {
|
it('should handle COMPLETE_ALL', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.COMPLETE_ALL
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.COMPLETE_ALL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Unmark if all todos are currently completed
|
// Unmark if all todos are currently completed
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.COMPLETE_ALL
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.COMPLETE_ALL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLEAR_COMPLETED', () => {
|
it('should handle CLEAR_COMPLETED', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.CLEAR_COMPLETED
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.CLEAR_COMPLETED,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
||||||
expect(
|
expect(
|
||||||
[{
|
[
|
||||||
|
{
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 0
|
id: 0,
|
||||||
}, {
|
},
|
||||||
type: types.CLEAR_COMPLETED
|
{
|
||||||
}, {
|
type: types.CLEAR_COMPLETED,
|
||||||
|
},
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Write more tests'
|
text: 'Write more tests',
|
||||||
}].reduce(todos, [{
|
},
|
||||||
|
].reduce(todos, [
|
||||||
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
completed: false,
|
completed: false,
|
||||||
text: 'Use Redux'
|
text: 'Use Redux',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
completed: false,
|
completed: false,
|
||||||
text: 'Write tests'
|
text: 'Write tests',
|
||||||
}])
|
},
|
||||||
).toEqual([{
|
])
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Write more tests',
|
text: 'Write more tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 2
|
id: 2,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Write tests',
|
text: 'Write tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,30 +3,30 @@ var webpack = require('webpack');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devtool: 'cheap-module-eval-source-map',
|
devtool: 'cheap-module-eval-source-map',
|
||||||
entry: [
|
entry: ['webpack-hot-middleware/client', './index'],
|
||||||
'webpack-hot-middleware/client',
|
|
||||||
'./index'
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
publicPath: '/static/'
|
publicPath: '/static/',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.optimize.OccurenceOrderPlugin(),
|
new webpack.optimize.OccurenceOrderPlugin(),
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new webpack.NoErrorsPlugin()
|
new webpack.NoErrorsPlugin(),
|
||||||
],
|
],
|
||||||
module: {
|
module: {
|
||||||
loaders: [{
|
loaders: [
|
||||||
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loaders: ['babel'],
|
loaders: ['babel'],
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
include: __dirname
|
include: __dirname,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
test: /\.css?$/,
|
test: /\.css?$/,
|
||||||
loaders: ['style', 'raw'],
|
loaders: ['style', 'raw'],
|
||||||
include: __dirname
|
include: __dirname,
|
||||||
}]
|
},
|
||||||
}
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Redux Saga Counter example</title>
|
<title>Redux Saga Counter example</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script type="text/javascript" src="/static/bundle.js"></script>
|
<script type="text/javascript" src="/static/bundle.js"></script>
|
||||||
|
|
|
@ -1,33 +1,27 @@
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const Counter = ({ value, onIncrement, onIncrementAsync, onDecrement, onIncrementIfOdd }) =>
|
const Counter = ({
|
||||||
|
value,
|
||||||
|
onIncrement,
|
||||||
|
onIncrementAsync,
|
||||||
|
onDecrement,
|
||||||
|
onIncrementIfOdd,
|
||||||
|
}) => (
|
||||||
<p>
|
<p>
|
||||||
Clicked: {value} times
|
Clicked: {value} times <button onClick={onIncrement}>+</button>{' '}
|
||||||
{' '}
|
<button onClick={onDecrement}>-</button>{' '}
|
||||||
<button onClick={onIncrement}>
|
<button onClick={onIncrementIfOdd}>Increment if odd</button>{' '}
|
||||||
+
|
<button onClick={onIncrementAsync}>Increment async</button>
|
||||||
</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={onDecrement}>
|
|
||||||
-
|
|
||||||
</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={onIncrementIfOdd}>
|
|
||||||
Increment if odd
|
|
||||||
</button>
|
|
||||||
{' '}
|
|
||||||
<button onClick={onIncrementAsync}>
|
|
||||||
Increment async
|
|
||||||
</button>
|
|
||||||
</p>
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
Counter.propTypes = {
|
Counter.propTypes = {
|
||||||
value: PropTypes.number.isRequired,
|
value: PropTypes.number.isRequired,
|
||||||
onIncrement: PropTypes.func.isRequired,
|
onIncrement: PropTypes.func.isRequired,
|
||||||
onDecrement: PropTypes.func.isRequired,
|
onDecrement: PropTypes.func.isRequired,
|
||||||
onIncrementAsync: PropTypes.func.isRequired,
|
onIncrementAsync: PropTypes.func.isRequired,
|
||||||
onIncrementIfOdd: PropTypes.func.isRequired
|
onIncrementIfOdd: PropTypes.func.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default Counter
|
export default Counter;
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
import "babel-polyfill"
|
import 'babel-polyfill';
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom';
|
||||||
import { createStore, applyMiddleware, compose } from 'redux'
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
import createSagaMiddleware from 'redux-saga'
|
import createSagaMiddleware from 'redux-saga';
|
||||||
// import sagaMonitor from './sagaMonitor'
|
// import sagaMonitor from './sagaMonitor'
|
||||||
|
|
||||||
import Counter from './components/Counter'
|
import Counter from './components/Counter';
|
||||||
import reducer from './reducers'
|
import reducer from './reducers';
|
||||||
import rootSaga from './sagas'
|
import rootSaga from './sagas';
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware(/* {sagaMonitor} */);
|
||||||
const sagaMiddleware = createSagaMiddleware(/* {sagaMonitor} */)
|
const composeEnhancers =
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
|
(window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ trace: true, traceLimit: 25 }) || compose;
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||||
|
trace: true,
|
||||||
|
traceLimit: 25,
|
||||||
|
})) ||
|
||||||
|
compose;
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
reducer,
|
reducer,
|
||||||
composeEnhancers(applyMiddleware(sagaMiddleware))
|
composeEnhancers(applyMiddleware(sagaMiddleware))
|
||||||
)
|
);
|
||||||
sagaMiddleware.run(rootSaga)
|
sagaMiddleware.run(rootSaga);
|
||||||
|
|
||||||
const action = type => store.dispatch({type})
|
const action = (type) => store.dispatch({ type });
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
|
@ -29,10 +33,11 @@ function render() {
|
||||||
onIncrement={() => action('INCREMENT')}
|
onIncrement={() => action('INCREMENT')}
|
||||||
onDecrement={() => action('DECREMENT')}
|
onDecrement={() => action('DECREMENT')}
|
||||||
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
|
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
|
||||||
onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
|
onIncrementAsync={() => action('INCREMENT_ASYNC')}
|
||||||
|
/>,
|
||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render()
|
render();
|
||||||
store.subscribe(render)
|
store.subscribe(render);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
export default function counter(state = 0, action) {
|
export default function counter(state = 0, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'INCREMENT':
|
case 'INCREMENT':
|
||||||
return state + 1
|
return state + 1;
|
||||||
case 'INCREMENT_IF_ODD':
|
case 'INCREMENT_IF_ODD':
|
||||||
return (state % 2 !== 0) ? state + 1 : state
|
return state % 2 !== 0 ? state + 1 : state;
|
||||||
case 'DECREMENT':
|
case 'DECREMENT':
|
||||||
return state - 1
|
return state - 1;
|
||||||
default:
|
default:
|
||||||
return state
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/* eslint-disable no-constant-condition */
|
/* eslint-disable no-constant-condition */
|
||||||
|
|
||||||
import { takeEvery } from 'redux-saga'
|
import { takeEvery } from 'redux-saga';
|
||||||
import { put, call } from 'redux-saga/effects'
|
import { put, call } from 'redux-saga/effects';
|
||||||
import { delay } from 'redux-saga'
|
import { delay } from 'redux-saga';
|
||||||
|
|
||||||
export function* incrementAsync() {
|
export function* incrementAsync() {
|
||||||
yield call(delay, 1000)
|
yield call(delay, 1000);
|
||||||
yield put({type: 'INCREMENT'})
|
yield put({ type: 'INCREMENT' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function* rootSaga() {
|
export default function* rootSaga() {
|
||||||
yield* takeEvery('INCREMENT_ASYNC', incrementAsync)
|
yield* takeEvery('INCREMENT_ASYNC', incrementAsync);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,22 @@ var webpack = require('webpack');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: [
|
entry: [path.join(__dirname, 'src', 'main')],
|
||||||
path.join(__dirname, 'src', 'main')
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
publicPath: '/static/'
|
publicPath: '/static/',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loaders: ['babel-loader'],
|
loaders: ['babel-loader'],
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/,
|
||||||
}]
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
port: 4003
|
port: 4003,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
const FILTER_TITLES = {
|
const FILTER_TITLES = {
|
||||||
[SHOW_ALL]: 'All',
|
[SHOW_ALL]: 'All',
|
||||||
[SHOW_ACTIVE]: 'Active',
|
[SHOW_ACTIVE]: 'Active',
|
||||||
[SHOW_COMPLETED]: 'Completed'
|
[SHOW_COMPLETED]: 'Completed',
|
||||||
};
|
};
|
||||||
|
|
||||||
class Footer extends Component {
|
class Footer extends Component {
|
||||||
|
@ -26,9 +30,11 @@ class Footer extends Component {
|
||||||
const { filter: selectedFilter, onShow } = this.props;
|
const { filter: selectedFilter, onShow } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a className={classnames({ selected: filter === selectedFilter })}
|
<a
|
||||||
|
className={classnames({ selected: filter === selectedFilter })}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
onClick={() => onShow(filter)}>
|
onClick={() => onShow(filter)}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -38,8 +44,7 @@ class Footer extends Component {
|
||||||
const { completedCount, onClearCompleted } = this.props;
|
const { completedCount, onClearCompleted } = this.props;
|
||||||
if (completedCount > 0) {
|
if (completedCount > 0) {
|
||||||
return (
|
return (
|
||||||
<button className="clear-completed"
|
<button className="clear-completed" onClick={onClearCompleted}>
|
||||||
onClick={onClearCompleted} >
|
|
||||||
Clear completed
|
Clear completed
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -51,11 +56,9 @@ class Footer extends Component {
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
{this.renderTodoCount()}
|
{this.renderTodoCount()}
|
||||||
<ul className="filters">
|
<ul className="filters">
|
||||||
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map(filter =>
|
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
||||||
<li key={filter}>
|
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
||||||
{this.renderFilterLink(filter)}
|
))}
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
{this.renderClearButton()}
|
{this.renderClearButton()}
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -68,7 +71,7 @@ Footer.propTypes = {
|
||||||
activeCount: PropTypes.number.isRequired,
|
activeCount: PropTypes.number.isRequired,
|
||||||
filter: PropTypes.string.isRequired,
|
filter: PropTypes.string.isRequired,
|
||||||
onClearCompleted: PropTypes.func.isRequired,
|
onClearCompleted: PropTypes.func.isRequired,
|
||||||
onShow: PropTypes.func.isRequired
|
onShow: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|
|
@ -13,16 +13,18 @@ class Header extends Component {
|
||||||
return (
|
return (
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<h1>todos</h1>
|
<h1>todos</h1>
|
||||||
<TodoTextInput newTodo
|
<TodoTextInput
|
||||||
|
newTodo
|
||||||
onSave={this.handleSave.bind(this)}
|
onSave={this.handleSave.bind(this)}
|
||||||
placeholder="What needs to be done?" />
|
placeholder="What needs to be done?"
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
addTodo: PropTypes.func.isRequired
|
addTodo: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Header;
|
export default Header;
|
||||||
|
|
|
@ -2,12 +2,16 @@ import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TodoItem from './TodoItem';
|
import TodoItem from './TodoItem';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED, SHOW_ACTIVE } from '../constants/TodoFilters';
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
const TODO_FILTERS = {
|
const TODO_FILTERS = {
|
||||||
[SHOW_ALL]: () => true,
|
[SHOW_ALL]: () => true,
|
||||||
[SHOW_ACTIVE]: todo => !todo.completed,
|
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
||||||
[SHOW_COMPLETED]: todo => todo.completed
|
[SHOW_COMPLETED]: (todo) => todo.completed,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MainSection extends Component {
|
class MainSection extends Component {
|
||||||
|
@ -17,7 +21,7 @@ class MainSection extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClearCompleted() {
|
handleClearCompleted() {
|
||||||
const atLeastOneCompleted = this.props.todos.some(todo => todo.completed);
|
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
||||||
if (atLeastOneCompleted) {
|
if (atLeastOneCompleted) {
|
||||||
this.props.actions.clearCompleted();
|
this.props.actions.clearCompleted();
|
||||||
}
|
}
|
||||||
|
@ -31,10 +35,12 @@ class MainSection extends Component {
|
||||||
const { todos, actions } = this.props;
|
const { todos, actions } = this.props;
|
||||||
if (todos.length > 0) {
|
if (todos.length > 0) {
|
||||||
return (
|
return (
|
||||||
<input className="toggle-all"
|
<input
|
||||||
|
className="toggle-all"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={completedCount === todos.length}
|
checked={completedCount === todos.length}
|
||||||
onChange={actions.completeAll} />
|
onChange={actions.completeAll}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,11 +52,13 @@ class MainSection extends Component {
|
||||||
|
|
||||||
if (todos.length) {
|
if (todos.length) {
|
||||||
return (
|
return (
|
||||||
<Footer completedCount={completedCount}
|
<Footer
|
||||||
|
completedCount={completedCount}
|
||||||
activeCount={activeCount}
|
activeCount={activeCount}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
onClearCompleted={this.handleClearCompleted.bind(this)}
|
onClearCompleted={this.handleClearCompleted.bind(this)}
|
||||||
onShow={this.handleShow.bind(this)} />
|
onShow={this.handleShow.bind(this)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,8 +68,8 @@ class MainSection extends Component {
|
||||||
const { filter } = this.state;
|
const { filter } = this.state;
|
||||||
|
|
||||||
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
||||||
const completedCount = todos.reduce((count, todo) =>
|
const completedCount = todos.reduce(
|
||||||
todo.completed ? count + 1 : count,
|
(count, todo) => (todo.completed ? count + 1 : count),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -69,9 +77,9 @@ class MainSection extends Component {
|
||||||
<section className="main">
|
<section className="main">
|
||||||
{this.renderToggleAll(completedCount)}
|
{this.renderToggleAll(completedCount)}
|
||||||
<ul className="todo-list">
|
<ul className="todo-list">
|
||||||
{filteredTodos.map(todo =>
|
{filteredTodos.map((todo) => (
|
||||||
<TodoItem key={todo.id} todo={todo} {...actions} />
|
<TodoItem key={todo.id} todo={todo} {...actions} />
|
||||||
)}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{this.renderFooter(completedCount)}
|
{this.renderFooter(completedCount)}
|
||||||
</section>
|
</section>
|
||||||
|
@ -81,7 +89,7 @@ class MainSection extends Component {
|
||||||
|
|
||||||
MainSection.propTypes = {
|
MainSection.propTypes = {
|
||||||
todos: PropTypes.array.isRequired,
|
todos: PropTypes.array.isRequired,
|
||||||
actions: PropTypes.object.isRequired
|
actions: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MainSection;
|
export default MainSection;
|
||||||
|
|
|
@ -7,7 +7,7 @@ class TodoItem extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
editing: false
|
editing: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,31 +30,36 @@ class TodoItem extends Component {
|
||||||
let element;
|
let element;
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
element = (
|
element = (
|
||||||
<TodoTextInput text={todo.text}
|
<TodoTextInput
|
||||||
|
text={todo.text}
|
||||||
editing={this.state.editing}
|
editing={this.state.editing}
|
||||||
onSave={(text) => this.handleSave(todo.id, text)} />
|
onSave={(text) => this.handleSave(todo.id, text)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
element = (
|
element = (
|
||||||
<div className="view">
|
<div className="view">
|
||||||
<input className="toggle"
|
<input
|
||||||
|
className="toggle"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={todo.completed}
|
checked={todo.completed}
|
||||||
onChange={() => completeTodo(todo.id)} />
|
onChange={() => completeTodo(todo.id)}
|
||||||
|
/>
|
||||||
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
||||||
{todo.text}
|
{todo.text}
|
||||||
</label>
|
</label>
|
||||||
<button className="destroy"
|
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
||||||
onClick={() => deleteTodo(todo.id)} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={classnames({
|
<li
|
||||||
|
className={classnames({
|
||||||
completed: todo.completed,
|
completed: todo.completed,
|
||||||
editing: this.state.editing
|
editing: this.state.editing,
|
||||||
})}>
|
})}
|
||||||
|
>
|
||||||
{element}
|
{element}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -65,7 +70,7 @@ TodoItem.propTypes = {
|
||||||
todo: PropTypes.object.isRequired,
|
todo: PropTypes.object.isRequired,
|
||||||
editTodo: PropTypes.func.isRequired,
|
editTodo: PropTypes.func.isRequired,
|
||||||
deleteTodo: PropTypes.func.isRequired,
|
deleteTodo: PropTypes.func.isRequired,
|
||||||
completeTodo: PropTypes.func.isRequired
|
completeTodo: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TodoItem;
|
export default TodoItem;
|
||||||
|
|
|
@ -6,7 +6,7 @@ class TodoTextInput extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
text: this.props.text || ''
|
text: this.props.text || '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,10 @@ class TodoTextInput extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<input className={
|
<input
|
||||||
classnames({
|
className={classnames({
|
||||||
edit: this.props.editing,
|
edit: this.props.editing,
|
||||||
'new-todo': this.props.newTodo
|
'new-todo': this.props.newTodo,
|
||||||
})}
|
})}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
|
@ -43,7 +43,8 @@ class TodoTextInput extends Component {
|
||||||
value={this.state.text}
|
value={this.state.text}
|
||||||
onBlur={this.handleBlur.bind(this)}
|
onBlur={this.handleBlur.bind(this)}
|
||||||
onChange={this.handleChange.bind(this)}
|
onChange={this.handleChange.bind(this)}
|
||||||
onKeyDown={this.handleSubmit.bind(this)} />
|
onKeyDown={this.handleSubmit.bind(this)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +54,7 @@ TodoTextInput.propTypes = {
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
editing: PropTypes.bool,
|
editing: PropTypes.bool,
|
||||||
newTodo: PropTypes.bool
|
newTodo: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TodoTextInput;
|
export default TodoTextInput;
|
||||||
|
|
|
@ -20,22 +20,19 @@ class App extends Component {
|
||||||
|
|
||||||
App.propTypes = {
|
App.propTypes = {
|
||||||
todos: PropTypes.array.isRequired,
|
todos: PropTypes.array.isRequired,
|
||||||
actions: PropTypes.object.isRequired
|
actions: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
todos: state.todos
|
todos: state.todos,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators(TodoActions, dispatch)
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(App);
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<title>Redux TodoMVC example</title>
|
<title>Redux TodoMVC example</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="todoapp" id="root">
|
<div class="todoapp" id="root"></div>
|
||||||
</div>
|
|
||||||
<script src="/static/bundle.js"></script>
|
<script src="/static/bundle.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { combineReducers } from 'redux';
|
||||||
import todos from './todos';
|
import todos from './todos';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
todos
|
todos,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -1,49 +1,65 @@
|
||||||
import { ADD_TODO, DELETE_TODO, EDIT_TODO, COMPLETE_TODO, COMPLETE_ALL, CLEAR_COMPLETED } from '../constants/ActionTypes';
|
import {
|
||||||
|
ADD_TODO,
|
||||||
|
DELETE_TODO,
|
||||||
|
EDIT_TODO,
|
||||||
|
COMPLETE_TODO,
|
||||||
|
COMPLETE_ALL,
|
||||||
|
CLEAR_COMPLETED,
|
||||||
|
} from '../constants/ActionTypes';
|
||||||
|
|
||||||
const initialState = [{
|
const initialState = [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
modified: new Date(),
|
modified: new Date(),
|
||||||
id: 0
|
id: 0,
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function todos(state = initialState, action) {
|
export default function todos(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ADD_TODO:
|
case ADD_TODO:
|
||||||
return [{
|
return [
|
||||||
|
{
|
||||||
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
||||||
completed: false,
|
completed: false,
|
||||||
modified: new Date(),
|
modified: new Date(),
|
||||||
text: action.text
|
text: action.text,
|
||||||
}, ...state];
|
},
|
||||||
|
...state,
|
||||||
|
];
|
||||||
|
|
||||||
case DELETE_TODO:
|
case DELETE_TODO:
|
||||||
return state.filter(todo =>
|
return state.filter((todo) => todo.id !== action.id);
|
||||||
todo.id !== action.id
|
|
||||||
);
|
|
||||||
|
|
||||||
case EDIT_TODO:
|
case EDIT_TODO:
|
||||||
return state.map(todo =>
|
return state.map((todo) =>
|
||||||
todo.id === action.id ?
|
todo.id === action.id
|
||||||
Object.assign({}, todo, { text: action.text, modified: new Date() }) :
|
? Object.assign({}, todo, { text: action.text, modified: new Date() })
|
||||||
todo
|
: todo
|
||||||
);
|
);
|
||||||
|
|
||||||
case COMPLETE_TODO:
|
case COMPLETE_TODO:
|
||||||
return state.map(todo =>
|
return state.map((todo) =>
|
||||||
todo.id === action.id ?
|
todo.id === action.id
|
||||||
Object.assign({}, todo, { completed: !todo.completed, modified: new Date() }) :
|
? Object.assign({}, todo, {
|
||||||
todo
|
completed: !todo.completed,
|
||||||
|
modified: new Date(),
|
||||||
|
})
|
||||||
|
: todo
|
||||||
);
|
);
|
||||||
|
|
||||||
case COMPLETE_ALL:
|
case COMPLETE_ALL:
|
||||||
const areAllMarked = state.every(todo => todo.completed);
|
const areAllMarked = state.every((todo) => todo.completed);
|
||||||
return state.map(todo => Object.assign({}, todo, {
|
return state.map((todo) =>
|
||||||
completed: !areAllMarked, modified: new Date()
|
Object.assign({}, todo, {
|
||||||
}));
|
completed: !areAllMarked,
|
||||||
|
modified: new Date(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
case CLEAR_COMPLETED:
|
case CLEAR_COMPLETED:
|
||||||
return state.filter(todo => todo.completed === false);
|
return state.filter((todo) => todo.completed === false);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -7,10 +7,15 @@ var app = new require('express')();
|
||||||
var port = 4002;
|
var port = 4002;
|
||||||
|
|
||||||
var compiler = webpack(config);
|
var compiler = webpack(config);
|
||||||
app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath }));
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
})
|
||||||
|
);
|
||||||
app.use(webpackHotMiddleware(compiler));
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
app.get("/", function(req, res) {
|
app.get('/', function (req, res) {
|
||||||
res.sendFile(__dirname + '/index.html');
|
res.sendFile(__dirname + '/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,6 +23,10 @@ app.listen(port, function(error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port);
|
console.info(
|
||||||
|
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
||||||
|
port,
|
||||||
|
port
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,11 +3,18 @@ import rootReducer from '../reducers';
|
||||||
import * as actionCreators from '../actions';
|
import * as actionCreators from '../actions';
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
export default function configureStore(preloadedState) {
|
||||||
const enhancer = window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
const enhancer =
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__({ actionCreators, serialize: true, trace: true });
|
window.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__({
|
||||||
|
actionCreators,
|
||||||
|
serialize: true,
|
||||||
|
trace: true,
|
||||||
|
});
|
||||||
if (!enhancer) {
|
if (!enhancer) {
|
||||||
console.warn('Install Redux DevTools Extension to inspect the app state: ' +
|
console.warn(
|
||||||
'https://github.com/zalmoxisus/redux-devtools-extension#installation')
|
'Install Redux DevTools Extension to inspect the app state: ' +
|
||||||
|
'https://github.com/zalmoxisus/redux-devtools-extension#installation'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = createStore(rootReducer, preloadedState, enhancer);
|
const store = createStore(rootReducer, preloadedState, enhancer);
|
||||||
|
@ -15,7 +22,7 @@ export default function configureStore(preloadedState) {
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
// Enable Webpack hot module replacement for reducers
|
// Enable Webpack hot module replacement for reducers
|
||||||
module.hot.accept('../reducers', () => {
|
module.hot.accept('../reducers', () => {
|
||||||
store.replaceReducer(require('../reducers').default)
|
store.replaceReducer(require('../reducers').default);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ describe('todo actions', () => {
|
||||||
it('addTodo should create ADD_TODO action', () => {
|
it('addTodo should create ADD_TODO action', () => {
|
||||||
expect(actions.addTodo('Use Redux')).toEqual({
|
expect(actions.addTodo('Use Redux')).toEqual({
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Use Redux'
|
text: 'Use Redux',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deleteTodo should create DELETE_TODO action', () => {
|
it('deleteTodo should create DELETE_TODO action', () => {
|
||||||
expect(actions.deleteTodo(1)).toEqual({
|
expect(actions.deleteTodo(1)).toEqual({
|
||||||
type: types.DELETE_TODO,
|
type: types.DELETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,26 +21,26 @@ describe('todo actions', () => {
|
||||||
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
||||||
type: types.EDIT_TODO,
|
type: types.EDIT_TODO,
|
||||||
id: 1,
|
id: 1,
|
||||||
text: 'Use Redux everywhere'
|
text: 'Use Redux everywhere',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completeTodo should create COMPLETE_TODO action', () => {
|
it('completeTodo should create COMPLETE_TODO action', () => {
|
||||||
expect(actions.completeTodo(1)).toEqual({
|
expect(actions.completeTodo(1)).toEqual({
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('completeAll should create COMPLETE_ALL action', () => {
|
it('completeAll should create COMPLETE_ALL action', () => {
|
||||||
expect(actions.completeAll()).toEqual({
|
expect(actions.completeAll()).toEqual({
|
||||||
type: types.COMPLETE_ALL
|
type: types.COMPLETE_ALL,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
||||||
expect(actions.clearCompleted('Use Redux')).toEqual({
|
expect(actions.clearCompleted('Use Redux')).toEqual({
|
||||||
type: types.CLEAR_COMPLETED
|
type: types.CLEAR_COMPLETED,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,13 +5,16 @@ import Footer from '../../components/Footer';
|
||||||
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
|
{
|
||||||
completedCount: 0,
|
completedCount: 0,
|
||||||
activeCount: 0,
|
activeCount: 0,
|
||||||
filter: SHOW_ALL,
|
filter: SHOW_ALL,
|
||||||
onClearCompleted: expect.createSpy(),
|
onClearCompleted: expect.createSpy(),
|
||||||
onShow: expect.createSpy()
|
onShow: expect.createSpy(),
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
renderer.render(<Footer {...props} />);
|
renderer.render(<Footer {...props} />);
|
||||||
|
@ -19,13 +22,14 @@ function setup(propOverrides) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output
|
output: output,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextContent(elem) {
|
function getTextContent(elem) {
|
||||||
const children = Array.isArray(elem.props.children) ?
|
const children = Array.isArray(elem.props.children)
|
||||||
elem.props.children : [elem.props.children];
|
? elem.props.children
|
||||||
|
: [elem.props.children];
|
||||||
|
|
||||||
return children.reduce(function concatText(out, child) {
|
return children.reduce(function concatText(out, child) {
|
||||||
// Children are either elements or text strings
|
// Children are either elements or text strings
|
||||||
|
@ -63,11 +67,13 @@ describe('components', () => {
|
||||||
expect(filter.type).toBe('li');
|
expect(filter.type).toBe('li');
|
||||||
const a = filter.props.children;
|
const a = filter.props.children;
|
||||||
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
||||||
expect(a.props.children).toBe({
|
expect(a.props.children).toBe(
|
||||||
|
{
|
||||||
0: 'All',
|
0: 'All',
|
||||||
1: 'Active',
|
1: 'Active',
|
||||||
2: 'Completed'
|
2: 'Completed',
|
||||||
}[i]);
|
}[i]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
const props = {
|
const props = {
|
||||||
addTodo: expect.createSpy()
|
addTodo: expect.createSpy(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
@ -16,7 +16,7 @@ function setup() {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,24 +7,30 @@ import Footer from '../../components/Footer';
|
||||||
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
todos: [{
|
{
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
actions: {
|
actions: {
|
||||||
editTodo: expect.createSpy(),
|
editTodo: expect.createSpy(),
|
||||||
deleteTodo: expect.createSpy(),
|
deleteTodo: expect.createSpy(),
|
||||||
completeTodo: expect.createSpy(),
|
completeTodo: expect.createSpy(),
|
||||||
completeAll: expect.createSpy(),
|
completeAll: expect.createSpy(),
|
||||||
clearCompleted: expect.createSpy()
|
clearCompleted: expect.createSpy(),
|
||||||
}
|
},
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
renderer.render(<MainSection {...props} />);
|
renderer.render(<MainSection {...props} />);
|
||||||
|
@ -33,7 +39,7 @@ function setup(propOverrides) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +61,15 @@ describe('components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be checked if all todos completed', () => {
|
it('should be checked if all todos completed', () => {
|
||||||
const { output } = setup({ todos: [{
|
const { output } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}]});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
const [toggle] = output.props.children;
|
const [toggle] = output.props.children;
|
||||||
expect(toggle.props.checked).toBe(true);
|
expect(toggle.props.checked).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -99,11 +109,15 @@ describe('components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
||||||
const { output, props } = setup({ todos: [{
|
const { output, props } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]});
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
const [, , footer] = output.props.children;
|
const [, , footer] = output.props.children;
|
||||||
footer.props.onClearCompleted();
|
footer.props.onClearCompleted();
|
||||||
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
||||||
|
|
|
@ -9,18 +9,16 @@ function setup( editing = false ) {
|
||||||
todo: {
|
todo: {
|
||||||
id: 0,
|
id: 0,
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false
|
completed: false,
|
||||||
},
|
},
|
||||||
editTodo: expect.createSpy(),
|
editTodo: expect.createSpy(),
|
||||||
deleteTodo: expect.createSpy(),
|
deleteTodo: expect.createSpy(),
|
||||||
completeTodo: expect.createSpy()
|
completeTodo: expect.createSpy(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
renderer.render(
|
renderer.render(<TodoItem {...props} />);
|
||||||
<TodoItem {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
@ -33,7 +31,7 @@ function setup( editing = false ) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,20 @@ import TestUtils from 'react-addons-test-utils';
|
||||||
import TodoTextInput from '../../components/TodoTextInput';
|
import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
function setup(propOverrides) {
|
function setup(propOverrides) {
|
||||||
const props = Object.assign({
|
const props = Object.assign(
|
||||||
|
{
|
||||||
onSave: expect.createSpy(),
|
onSave: expect.createSpy(),
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
placeholder: 'What needs to be done?',
|
placeholder: 'What needs to be done?',
|
||||||
editing: false,
|
editing: false,
|
||||||
newTodo: false
|
newTodo: false,
|
||||||
}, propOverrides);
|
},
|
||||||
|
propOverrides
|
||||||
|
);
|
||||||
|
|
||||||
const renderer = TestUtils.createRenderer();
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
renderer.render(
|
renderer.render(<TodoTextInput {...props} />);
|
||||||
<TodoTextInput {...props} />
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = renderer.getRenderOutput();
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ function setup(propOverrides) {
|
||||||
return {
|
return {
|
||||||
props: props,
|
props: props,
|
||||||
output: output,
|
output: output,
|
||||||
renderer: renderer
|
renderer: renderer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,240 +4,322 @@ import * as types from '../../constants/ActionTypes';
|
||||||
|
|
||||||
describe('todos reducer', () => {
|
describe('todos reducer', () => {
|
||||||
it('should handle initial state', () => {
|
it('should handle initial state', () => {
|
||||||
expect(
|
expect(todos(undefined, {})).toEqual([
|
||||||
todos(undefined, {})
|
{
|
||||||
).toEqual([{
|
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle ADD_TODO', () => {
|
it('should handle ADD_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([], {
|
todos([], {
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Run the tests'
|
text: 'Run the tests',
|
||||||
})
|
})
|
||||||
).toEqual([{
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Run the tests'
|
text: 'Run the tests',
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Fix the tests'
|
text: 'Fix the tests',
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 2
|
id: 2,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle DELETE_TODO', () => {
|
it('should handle DELETE_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.DELETE_TODO,
|
type: types.DELETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle EDIT_TODO', () => {
|
it('should handle EDIT_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.EDIT_TODO,
|
type: types.EDIT_TODO,
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Fix the tests',
|
text: 'Fix the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle COMPLETE_TODO', () => {
|
it('should handle COMPLETE_TODO', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 1
|
id: 1,
|
||||||
})
|
}
|
||||||
).toEqual([{
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle COMPLETE_ALL', () => {
|
it('should handle COMPLETE_ALL', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.COMPLETE_ALL
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.COMPLETE_ALL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Unmark if all todos are currently completed
|
// Unmark if all todos are currently completed
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.COMPLETE_ALL
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.COMPLETE_ALL,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLEAR_COMPLETED', () => {
|
it('should handle CLEAR_COMPLETED', () => {
|
||||||
expect(
|
expect(
|
||||||
todos([{
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
text: 'Run the tests',
|
text: 'Run the tests',
|
||||||
completed: true,
|
completed: true,
|
||||||
id: 1
|
id: 1,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}], {
|
},
|
||||||
type: types.CLEAR_COMPLETED
|
],
|
||||||
})
|
{
|
||||||
).toEqual([{
|
type: types.CLEAR_COMPLETED,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Use Redux',
|
text: 'Use Redux',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 0
|
id: 0,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
||||||
expect(
|
expect(
|
||||||
[{
|
[
|
||||||
|
{
|
||||||
type: types.COMPLETE_TODO,
|
type: types.COMPLETE_TODO,
|
||||||
id: 0
|
id: 0,
|
||||||
}, {
|
},
|
||||||
type: types.CLEAR_COMPLETED
|
{
|
||||||
}, {
|
type: types.CLEAR_COMPLETED,
|
||||||
|
},
|
||||||
|
{
|
||||||
type: types.ADD_TODO,
|
type: types.ADD_TODO,
|
||||||
text: 'Write more tests'
|
text: 'Write more tests',
|
||||||
}].reduce(todos, [{
|
},
|
||||||
|
].reduce(todos, [
|
||||||
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
completed: false,
|
completed: false,
|
||||||
text: 'Use Redux'
|
text: 'Use Redux',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
completed: false,
|
completed: false,
|
||||||
text: 'Write tests'
|
text: 'Write tests',
|
||||||
}])
|
},
|
||||||
).toEqual([{
|
])
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
text: 'Write more tests',
|
text: 'Write more tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 2
|
id: 2,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
text: 'Write tests',
|
text: 'Write tests',
|
||||||
completed: false,
|
completed: false,
|
||||||
id: 1
|
id: 1,
|
||||||
}]);
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,27 +4,25 @@ var webpack = require('webpack');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: [
|
entry: ['webpack-hot-middleware/client', './index'],
|
||||||
'webpack-hot-middleware/client',
|
|
||||||
'./index'
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
filename: 'bundle.js',
|
filename: 'bundle.js',
|
||||||
publicPath: '/static/'
|
publicPath: '/static/',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||||
new webpack.HotModuleReplacementPlugin()
|
|
||||||
],
|
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
loaders: ['babel-loader'],
|
loaders: ['babel-loader'],
|
||||||
exclude: /node_modules/
|
exclude: /node_modules/,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
test: /\.css?$/,
|
test: /\.css?$/,
|
||||||
loaders: ['style-loader', 'raw-loader'],
|
loaders: ['style-loader', 'raw-loader'],
|
||||||
include: __dirname
|
include: __dirname,
|
||||||
}]
|
},
|
||||||
}
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,14 +19,19 @@ function copy(dest) {
|
||||||
* common tasks
|
* common tasks
|
||||||
*/
|
*/
|
||||||
gulp.task('replace-webpack-code', () => {
|
gulp.task('replace-webpack-code', () => {
|
||||||
const replaceTasks = [{
|
const replaceTasks = [
|
||||||
|
{
|
||||||
from: './webpack/replace/JsonpMainTemplate.runtime.js',
|
from: './webpack/replace/JsonpMainTemplate.runtime.js',
|
||||||
to: './node_modules/webpack/lib/JsonpMainTemplate.runtime.js'
|
to: './node_modules/webpack/lib/JsonpMainTemplate.runtime.js',
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
from: './webpack/replace/log-apply-result.js',
|
from: './webpack/replace/log-apply-result.js',
|
||||||
to: './node_modules/webpack/hot/log-apply-result.js'
|
to: './node_modules/webpack/hot/log-apply-result.js',
|
||||||
}];
|
},
|
||||||
replaceTasks.forEach(task => fs.writeFileSync(task.to, fs.readFileSync(task.from)));
|
];
|
||||||
|
replaceTasks.forEach((task) =>
|
||||||
|
fs.writeFileSync(task.to, fs.readFileSync(task.from))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -44,15 +49,19 @@ gulp.task('webpack:dev', (callback) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('views:dev', () => {
|
gulp.task('views:dev', () => {
|
||||||
gulp.src('./src/browser/views/*.pug')
|
gulp
|
||||||
.pipe(jade({
|
.src('./src/browser/views/*.pug')
|
||||||
locals: { env: 'dev' }
|
.pipe(
|
||||||
}))
|
jade({
|
||||||
|
locals: { env: 'dev' },
|
||||||
|
})
|
||||||
|
)
|
||||||
.pipe(gulp.dest('./dev'));
|
.pipe(gulp.dest('./dev'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy:dev', () => {
|
gulp.task('copy:dev', () => {
|
||||||
gulp.src('./src/browser/extension/manifest.json')
|
gulp
|
||||||
|
.src('./src/browser/extension/manifest.json')
|
||||||
.pipe(rename('manifest.json'))
|
.pipe(rename('manifest.json'))
|
||||||
.pipe(gulp.dest('./dev'));
|
.pipe(gulp.dest('./dev'));
|
||||||
copy('./dev');
|
copy('./dev');
|
||||||
|
@ -87,29 +96,34 @@ gulp.task('webpack:build:extension', (callback) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('views:build:extension', () => {
|
gulp.task('views:build:extension', () => {
|
||||||
gulp.src([
|
gulp
|
||||||
'./src/browser/views/*.pug'
|
.src(['./src/browser/views/*.pug'])
|
||||||
])
|
.pipe(
|
||||||
.pipe(jade({
|
jade({
|
||||||
locals: { env: 'prod' }
|
locals: { env: 'prod' },
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
.pipe(gulp.dest('./build/extension'));
|
.pipe(gulp.dest('./build/extension'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy:build:extension', () => {
|
gulp.task('copy:build:extension', () => {
|
||||||
gulp.src('./src/browser/extension/manifest.json')
|
gulp
|
||||||
|
.src('./src/browser/extension/manifest.json')
|
||||||
.pipe(rename('manifest.json'))
|
.pipe(rename('manifest.json'))
|
||||||
.pipe(gulp.dest('./build/extension'));
|
.pipe(gulp.dest('./build/extension'));
|
||||||
copy('./build/extension');
|
copy('./build/extension');
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy:build:firefox', ['build:extension'], () => {
|
gulp.task('copy:build:firefox', ['build:extension'], () => {
|
||||||
gulp.src([
|
gulp
|
||||||
'./build/extension/**', '!./build/extension/js/redux-devtools-extension.js'
|
.src([
|
||||||
|
'./build/extension/**',
|
||||||
|
'!./build/extension/js/redux-devtools-extension.js',
|
||||||
])
|
])
|
||||||
.pipe(gulp.dest('./build/firefox'))
|
.pipe(gulp.dest('./build/firefox'))
|
||||||
.on('finish', function () {
|
.on('finish', function () {
|
||||||
gulp.src('./src/browser/firefox/manifest.json')
|
gulp
|
||||||
|
.src('./src/browser/firefox/manifest.json')
|
||||||
.pipe(gulp.dest('./build/firefox'));
|
.pipe(gulp.dest('./build/firefox'));
|
||||||
});
|
});
|
||||||
copy('./build/firefox');
|
copy('./build/firefox');
|
||||||
|
@ -120,13 +134,15 @@ gulp.task('copy:build:firefox', ['build:extension'], () => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gulp.task('compress:extension', () => {
|
gulp.task('compress:extension', () => {
|
||||||
gulp.src('build/extension/**')
|
gulp
|
||||||
|
.src('build/extension/**')
|
||||||
.pipe(zip('extension.zip'))
|
.pipe(zip('extension.zip'))
|
||||||
.pipe(gulp.dest('./build'));
|
.pipe(gulp.dest('./build'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('compress:firefox', () => {
|
gulp.task('compress:firefox', () => {
|
||||||
gulp.src('build/firefox/**')
|
gulp
|
||||||
|
.src('build/firefox/**')
|
||||||
.pipe(zip('firefox.zip'))
|
.pipe(zip('firefox.zip'))
|
||||||
.pipe(gulp.dest('./build'));
|
.pipe(gulp.dest('./build'));
|
||||||
});
|
});
|
||||||
|
@ -140,23 +156,40 @@ gulp.task('views:watch', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('copy:watch', () => {
|
gulp.task('copy:watch', () => {
|
||||||
gulp.watch(['./src/browser/extension/manifest.json', './src/assets/**/*'], ['copy:dev']);
|
gulp.watch(
|
||||||
|
['./src/browser/extension/manifest.json', './src/assets/**/*'],
|
||||||
|
['copy:dev']
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:chrome', () => {
|
gulp.task('test:chrome', () => {
|
||||||
crdv.start();
|
crdv.start();
|
||||||
return gulp.src('./test/chrome/*.spec.js')
|
return gulp
|
||||||
|
.src('./test/chrome/*.spec.js')
|
||||||
.pipe(mocha({ require: ['babel-polyfill', 'co-mocha'] }))
|
.pipe(mocha({ require: ['babel-polyfill', 'co-mocha'] }))
|
||||||
.on('end', () => crdv.stop());
|
.on('end', () => crdv.stop());
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('test:electron', () => {
|
gulp.task('test:electron', () => {
|
||||||
crdv.start();
|
crdv.start();
|
||||||
return gulp.src('./test/electron/*.spec.js')
|
return gulp
|
||||||
|
.src('./test/electron/*.spec.js')
|
||||||
.pipe(mocha({ require: ['babel-polyfill', 'co-mocha'] }))
|
.pipe(mocha({ require: ['babel-polyfill', 'co-mocha'] }))
|
||||||
.on('end', () => crdv.stop());
|
.on('end', () => crdv.stop());
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('default', ['replace-webpack-code', 'webpack:dev', 'views:dev', 'copy:dev', 'views:watch', 'copy:watch']);
|
gulp.task('default', [
|
||||||
gulp.task('build:extension', ['replace-webpack-code', 'webpack:build:extension', 'views:build:extension', 'copy:build:extension']);
|
'replace-webpack-code',
|
||||||
|
'webpack:dev',
|
||||||
|
'views:dev',
|
||||||
|
'copy:dev',
|
||||||
|
'views:watch',
|
||||||
|
'copy:watch',
|
||||||
|
]);
|
||||||
|
gulp.task('build:extension', [
|
||||||
|
'replace-webpack-code',
|
||||||
|
'webpack:build:extension',
|
||||||
|
'views:build:extension',
|
||||||
|
'copy:build:extension',
|
||||||
|
]);
|
||||||
gulp.task('build:firefox', ['copy:build:firefox']);
|
gulp.task('build:firefox', ['copy:build:firefox']);
|
||||||
|
|
|
@ -3,55 +3,72 @@ import mapValues from 'lodash/mapValues';
|
||||||
export const FilterState = {
|
export const FilterState = {
|
||||||
DO_NOT_FILTER: 'DO_NOT_FILTER',
|
DO_NOT_FILTER: 'DO_NOT_FILTER',
|
||||||
BLACKLIST_SPECIFIC: 'BLACKLIST_SPECIFIC',
|
BLACKLIST_SPECIFIC: 'BLACKLIST_SPECIFIC',
|
||||||
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC'
|
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLocalFilter(config) {
|
export function getLocalFilter(config) {
|
||||||
if (config.actionsBlacklist || config.actionsWhitelist) {
|
if (config.actionsBlacklist || config.actionsWhitelist) {
|
||||||
return {
|
return {
|
||||||
whitelist: Array.isArray(config.actionsWhitelist) ? config.actionsWhitelist.join('|') : config.actionsWhitelist,
|
whitelist: Array.isArray(config.actionsWhitelist)
|
||||||
blacklist: Array.isArray(config.actionsBlacklist) ? config.actionsBlacklist.join('|') : config.actionsBlacklist
|
? config.actionsWhitelist.join('|')
|
||||||
|
: config.actionsWhitelist,
|
||||||
|
blacklist: Array.isArray(config.actionsBlacklist)
|
||||||
|
? config.actionsBlacklist.join('|')
|
||||||
|
: config.actionsBlacklist,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const noFiltersApplied = (localFilter) => (
|
export const noFiltersApplied = (localFilter) =>
|
||||||
// !predicate &&
|
// !predicate &&
|
||||||
!localFilter && (!window.devToolsOptions || !window.devToolsOptions.filter ||
|
!localFilter &&
|
||||||
window.devToolsOptions.filter === FilterState.DO_NOT_FILTER)
|
(!window.devToolsOptions ||
|
||||||
);
|
!window.devToolsOptions.filter ||
|
||||||
|
window.devToolsOptions.filter === FilterState.DO_NOT_FILTER);
|
||||||
|
|
||||||
export function isFiltered(action, localFilter) {
|
export function isFiltered(action, localFilter) {
|
||||||
if (
|
if (
|
||||||
noFiltersApplied(localFilter) ||
|
noFiltersApplied(localFilter) ||
|
||||||
typeof action !== 'string' && typeof action.type.match !== 'function'
|
(typeof action !== 'string' && typeof action.type.match !== 'function')
|
||||||
) return false;
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
const { whitelist, blacklist } = localFilter || window.devToolsOptions || {};
|
const { whitelist, blacklist } = localFilter || window.devToolsOptions || {};
|
||||||
const actionType = action.type || action;
|
const actionType = action.type || action;
|
||||||
return (
|
return (
|
||||||
whitelist && !actionType.match(whitelist) ||
|
(whitelist && !actionType.match(whitelist)) ||
|
||||||
blacklist && actionType.match(blacklist)
|
(blacklist && actionType.match(blacklist))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterActions(actionsById, actionSanitizer) {
|
function filterActions(actionsById, actionSanitizer) {
|
||||||
if (!actionSanitizer) return actionsById;
|
if (!actionSanitizer) return actionsById;
|
||||||
return mapValues(actionsById, (action, id) => (
|
return mapValues(actionsById, (action, id) => ({
|
||||||
{ ...action, action: actionSanitizer(action.action, id) }
|
...action,
|
||||||
));
|
action: actionSanitizer(action.action, id),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterStates(computedStates, stateSanitizer) {
|
function filterStates(computedStates, stateSanitizer) {
|
||||||
if (!stateSanitizer) return computedStates;
|
if (!stateSanitizer) return computedStates;
|
||||||
return computedStates.map((state, idx) => (
|
return computedStates.map((state, idx) => ({
|
||||||
{ ...state, state: stateSanitizer(state.state, idx) }
|
...state,
|
||||||
));
|
state: stateSanitizer(state.state, idx),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterState(state, type, localFilter, stateSanitizer, actionSanitizer, nextActionId, predicate) {
|
export function filterState(
|
||||||
if (type === 'ACTION') return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1);
|
state,
|
||||||
|
type,
|
||||||
|
localFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
nextActionId,
|
||||||
|
predicate
|
||||||
|
) {
|
||||||
|
if (type === 'ACTION')
|
||||||
|
return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1);
|
||||||
else if (type !== 'STATE') return state;
|
else if (type !== 'STATE') return state;
|
||||||
|
|
||||||
if (predicate || !noFiltersApplied(localFilter)) {
|
if (predicate || !noFiltersApplied(localFilter)) {
|
||||||
|
@ -74,11 +91,14 @@ export function filterState(state, type, localFilter, stateSanitizer, actionSani
|
||||||
|
|
||||||
filteredStagedActionIds.push(id);
|
filteredStagedActionIds.push(id);
|
||||||
filteredComputedStates.push(
|
filteredComputedStates.push(
|
||||||
stateSanitizer ? { ...liftedState, state: stateSanitizer(currState, idx) } : liftedState
|
stateSanitizer
|
||||||
|
? { ...liftedState, state: stateSanitizer(currState, idx) }
|
||||||
|
: liftedState
|
||||||
);
|
);
|
||||||
if (actionSanitizer) {
|
if (actionSanitizer) {
|
||||||
sanitizedActionsById[id] = {
|
sanitizedActionsById[id] = {
|
||||||
...liftedAction, action: actionSanitizer(currAction, id)
|
...liftedAction,
|
||||||
|
action: actionSanitizer(currAction, id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -87,7 +107,7 @@ export function filterState(state, type, localFilter, stateSanitizer, actionSani
|
||||||
...state,
|
...state,
|
||||||
actionsById: sanitizedActionsById || actionsById,
|
actionsById: sanitizedActionsById || actionsById,
|
||||||
stagedActionIds: filteredStagedActionIds,
|
stagedActionIds: filteredStagedActionIds,
|
||||||
computedStates: filteredComputedStates
|
computedStates: filteredComputedStates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,12 +115,17 @@ export function filterState(state, type, localFilter, stateSanitizer, actionSani
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
actionsById: filterActions(state.actionsById, actionSanitizer),
|
actionsById: filterActions(state.actionsById, actionSanitizer),
|
||||||
computedStates: filterStates(state.computedStates, stateSanitizer)
|
computedStates: filterStates(state.computedStates, stateSanitizer),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startingFrom(
|
export function startingFrom(
|
||||||
sendingActionId, state, localFilter, stateSanitizer, actionSanitizer, predicate
|
sendingActionId,
|
||||||
|
state,
|
||||||
|
localFilter,
|
||||||
|
stateSanitizer,
|
||||||
|
actionSanitizer,
|
||||||
|
predicate
|
||||||
) {
|
) {
|
||||||
const stagedActionIds = state.stagedActionIds;
|
const stagedActionIds = state.stagedActionIds;
|
||||||
if (sendingActionId <= stagedActionIds[1]) return state;
|
if (sendingActionId <= stagedActionIds[1]) return state;
|
||||||
|
@ -124,17 +149,21 @@ export function startingFrom(
|
||||||
|
|
||||||
if (shouldFilter) {
|
if (shouldFilter) {
|
||||||
if (
|
if (
|
||||||
predicate && !predicate(currState.state, currAction.action) ||
|
(predicate && !predicate(currState.state, currAction.action)) ||
|
||||||
isFiltered(currAction.action, localFilter)
|
isFiltered(currAction.action, localFilter)
|
||||||
) continue;
|
)
|
||||||
|
continue;
|
||||||
filteredStagedActionIds.push(key);
|
filteredStagedActionIds.push(key);
|
||||||
if (i < index) continue;
|
if (i < index) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
newActionsById[key] = !actionSanitizer ? currAction :
|
newActionsById[key] = !actionSanitizer
|
||||||
{ ...currAction, action: actionSanitizer(currAction.action, key) };
|
? currAction
|
||||||
|
: { ...currAction, action: actionSanitizer(currAction.action, key) };
|
||||||
newComputedStates.push(
|
newComputedStates.push(
|
||||||
!stateSanitizer ? currState : { ...currState, state: stateSanitizer(currState.state, i) }
|
!stateSanitizer
|
||||||
|
? currState
|
||||||
|
: { ...currState, state: stateSanitizer(currState.state, i) }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +174,6 @@ export function startingFrom(
|
||||||
computedStates: newComputedStates,
|
computedStates: newComputedStates,
|
||||||
stagedActionIds: filteredStagedActionIds,
|
stagedActionIds: filteredStagedActionIds,
|
||||||
currentStateIndex: state.currentStateIndex,
|
currentStateIndex: state.currentStateIndex,
|
||||||
nextActionId: state.nextActionId
|
nextActionId: state.nextActionId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,38 +3,55 @@ import jsan from 'jsan';
|
||||||
import seralizeImmutable from 'remotedev-serialize/immutable/serialize';
|
import seralizeImmutable from 'remotedev-serialize/immutable/serialize';
|
||||||
|
|
||||||
function deprecate(param) {
|
function deprecate(param) {
|
||||||
console.warn(`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1`); // eslint-disable-line
|
console.warn(
|
||||||
|
`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1`
|
||||||
|
); // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function importState(state, { deserializeState, deserializeAction, serialize }) {
|
export default function importState(
|
||||||
|
state,
|
||||||
|
{ deserializeState, deserializeAction, serialize }
|
||||||
|
) {
|
||||||
if (!state) return undefined;
|
if (!state) return undefined;
|
||||||
let parse = jsan.parse;
|
let parse = jsan.parse;
|
||||||
if (serialize) {
|
if (serialize) {
|
||||||
if (serialize.immutable) {
|
if (serialize.immutable) {
|
||||||
parse = v => jsan.parse(v, seralizeImmutable(
|
parse = (v) =>
|
||||||
serialize.immutable, serialize.refs, serialize.replacer, serialize.reviver
|
jsan.parse(
|
||||||
).reviver);
|
v,
|
||||||
|
seralizeImmutable(
|
||||||
|
serialize.immutable,
|
||||||
|
serialize.refs,
|
||||||
|
serialize.replacer,
|
||||||
|
serialize.reviver
|
||||||
|
).reviver
|
||||||
|
);
|
||||||
} else if (serialize.reviver) {
|
} else if (serialize.reviver) {
|
||||||
parse = v => jsan.parse(v, serialize.reviver);
|
parse = (v) => jsan.parse(v, serialize.reviver);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let preloadedState;
|
let preloadedState;
|
||||||
let nextLiftedState = parse(state);
|
let nextLiftedState = parse(state);
|
||||||
if (nextLiftedState.payload) {
|
if (nextLiftedState.payload) {
|
||||||
if (nextLiftedState.preloadedState) preloadedState = parse(nextLiftedState.preloadedState);
|
if (nextLiftedState.preloadedState)
|
||||||
|
preloadedState = parse(nextLiftedState.preloadedState);
|
||||||
nextLiftedState = parse(nextLiftedState.payload);
|
nextLiftedState = parse(nextLiftedState.payload);
|
||||||
}
|
}
|
||||||
if (deserializeState) {
|
if (deserializeState) {
|
||||||
deprecate('deserializeState');
|
deprecate('deserializeState');
|
||||||
if (typeof nextLiftedState.computedStates !== 'undefined') {
|
if (typeof nextLiftedState.computedStates !== 'undefined') {
|
||||||
nextLiftedState.computedStates = nextLiftedState.computedStates.map(computedState => ({
|
nextLiftedState.computedStates = nextLiftedState.computedStates.map(
|
||||||
|
(computedState) => ({
|
||||||
...computedState,
|
...computedState,
|
||||||
state: deserializeState(computedState.state)
|
state: deserializeState(computedState.state),
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (typeof nextLiftedState.committedState !== 'undefined') {
|
if (typeof nextLiftedState.committedState !== 'undefined') {
|
||||||
nextLiftedState.committedState = deserializeState(nextLiftedState.committedState);
|
nextLiftedState.committedState = deserializeState(
|
||||||
|
nextLiftedState.committedState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (typeof preloadedState !== 'undefined') {
|
if (typeof preloadedState !== 'undefined') {
|
||||||
preloadedState = deserializeState(preloadedState);
|
preloadedState = deserializeState(preloadedState);
|
||||||
|
@ -42,10 +59,13 @@ export default function importState(state, { deserializeState, deserializeAction
|
||||||
}
|
}
|
||||||
if (deserializeAction) {
|
if (deserializeAction) {
|
||||||
deprecate('deserializeAction');
|
deprecate('deserializeAction');
|
||||||
nextLiftedState.actionsById = mapValues(nextLiftedState.actionsById, liftedAction => ({
|
nextLiftedState.actionsById = mapValues(
|
||||||
|
nextLiftedState.actionsById,
|
||||||
|
(liftedAction) => ({
|
||||||
...liftedAction,
|
...liftedAction,
|
||||||
action: deserializeAction(liftedAction.action)
|
action: deserializeAction(liftedAction.action),
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { nextLiftedState, preloadedState };
|
return { nextLiftedState, preloadedState };
|
||||||
|
|
|
@ -21,20 +21,29 @@ function tryCatchStringify(obj) {
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
if (process.env.NODE_ENV !== 'production') console.log('Failed to stringify', err);
|
if (process.env.NODE_ENV !== 'production')
|
||||||
|
console.log('Failed to stringify', err);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
return jsan.stringify(obj, windowReplacer, null, { circular: '[CIRCULAR]', date: true });
|
return jsan.stringify(obj, windowReplacer, null, {
|
||||||
|
circular: '[CIRCULAR]',
|
||||||
|
date: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let stringifyWarned;
|
let stringifyWarned;
|
||||||
function stringify(obj, serialize) {
|
function stringify(obj, serialize) {
|
||||||
const str = typeof serialize === 'undefined' ? tryCatchStringify(obj) :
|
const str =
|
||||||
jsan.stringify(obj, serialize.replacer, null, serialize.options);
|
typeof serialize === 'undefined'
|
||||||
|
? tryCatchStringify(obj)
|
||||||
|
: jsan.stringify(obj, serialize.replacer, null, serialize.options);
|
||||||
|
|
||||||
if (!stringifyWarned && str && str.length > 16 * 1024 * 1024) { // 16 MB
|
if (!stringifyWarned && str && str.length > 16 * 1024 * 1024) {
|
||||||
|
// 16 MB
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.warn('Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.');
|
console.warn(
|
||||||
|
'Application state or actions payloads are too large making Redux DevTools serialization slow and consuming a lot of memory. See https://git.io/fpcP5 on how to configure it.'
|
||||||
|
);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
stringifyWarned = true;
|
stringifyWarned = true;
|
||||||
}
|
}
|
||||||
|
@ -48,22 +57,34 @@ export function getSeralizeParameter(config, param) {
|
||||||
if (serialize === true) return { options: true };
|
if (serialize === true) return { options: true };
|
||||||
if (serialize.immutable) {
|
if (serialize.immutable) {
|
||||||
const immutableSerializer = seralizeImmutable(
|
const immutableSerializer = seralizeImmutable(
|
||||||
serialize.immutable, serialize.refs, serialize.replacer, serialize.reviver
|
serialize.immutable,
|
||||||
|
serialize.refs,
|
||||||
|
serialize.replacer,
|
||||||
|
serialize.reviver
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
replacer: immutableSerializer.replacer,
|
replacer: immutableSerializer.replacer,
|
||||||
reviver: immutableSerializer.reviver,
|
reviver: immutableSerializer.reviver,
|
||||||
options: typeof serialize.options === 'object' ?
|
options:
|
||||||
{ ...immutableSerializer.options, ...serialize.options } : immutableSerializer.options
|
typeof serialize.options === 'object'
|
||||||
|
? { ...immutableSerializer.options, ...serialize.options }
|
||||||
|
: immutableSerializer.options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!serialize.replacer && !serialize.reviver) return { options: serialize.options };
|
if (!serialize.replacer && !serialize.reviver)
|
||||||
return { replacer: serialize.replacer, reviver: serialize.reviver, options: serialize.options || true };
|
return { options: serialize.options };
|
||||||
|
return {
|
||||||
|
replacer: serialize.replacer,
|
||||||
|
reviver: serialize.reviver,
|
||||||
|
options: serialize.options || true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = config[param];
|
const value = config[param];
|
||||||
if (typeof value === 'undefined') return undefined;
|
if (typeof value === 'undefined') return undefined;
|
||||||
console.warn(`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1`); // eslint-disable-line
|
console.warn(
|
||||||
|
`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead: https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1`
|
||||||
|
); // eslint-disable-line
|
||||||
|
|
||||||
if (typeof serializeState === 'boolean') return { options: value };
|
if (typeof serializeState === 'boolean') return { options: value };
|
||||||
if (typeof serializeState === 'function') return { replacer: value };
|
if (typeof serializeState === 'function') return { replacer: value };
|
||||||
|
@ -94,10 +115,16 @@ function getStackTrace(config, toExcludeFromTrace) {
|
||||||
}
|
}
|
||||||
stack = error.stack;
|
stack = error.stack;
|
||||||
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit;
|
if (prevStackTraceLimit) Error.stackTraceLimit = prevStackTraceLimit;
|
||||||
if (extraFrames || typeof Error.stackTraceLimit !== 'number' || Error.stackTraceLimit > traceLimit) {
|
if (
|
||||||
|
extraFrames ||
|
||||||
|
typeof Error.stackTraceLimit !== 'number' ||
|
||||||
|
Error.stackTraceLimit > traceLimit
|
||||||
|
) {
|
||||||
const frames = stack.split('\n');
|
const frames = stack.split('\n');
|
||||||
if (frames.length > traceLimit) {
|
if (frames.length > traceLimit) {
|
||||||
stack = frames.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0)).join('\n');
|
stack = frames
|
||||||
|
.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0))
|
||||||
|
.join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stack;
|
return stack;
|
||||||
|
@ -106,7 +133,8 @@ function getStackTrace(config, toExcludeFromTrace) {
|
||||||
function amendActionType(action, config, toExcludeFromTrace) {
|
function amendActionType(action, config, toExcludeFromTrace) {
|
||||||
let timestamp = Date.now();
|
let timestamp = Date.now();
|
||||||
let stack = getStackTrace(config, toExcludeFromTrace);
|
let stack = getStackTrace(config, toExcludeFromTrace);
|
||||||
if (typeof action === 'string') return { action: { type: action }, timestamp, stack };
|
if (typeof action === 'string')
|
||||||
|
return { action: { type: action }, timestamp, stack };
|
||||||
if (!action.type) return { action: { type: 'update' }, timestamp, stack };
|
if (!action.type) return { action: { type: 'update' }, timestamp, stack };
|
||||||
if (action.action) return stack ? { stack, ...action } : action;
|
if (action.action) return stack ? { stack, ...action } : action;
|
||||||
return { action, timestamp, stack };
|
return { action, timestamp, stack };
|
||||||
|
@ -117,7 +145,12 @@ export function toContentScript(message, serializeState, serializeAction) {
|
||||||
message.action = stringify(message.action, serializeAction);
|
message.action = stringify(message.action, serializeAction);
|
||||||
message.payload = stringify(message.payload, serializeState);
|
message.payload = stringify(message.payload, serializeState);
|
||||||
} else if (message.type === 'STATE' || message.type === 'PARTIAL_STATE') {
|
} else if (message.type === 'STATE' || message.type === 'PARTIAL_STATE') {
|
||||||
const { actionsById, computedStates, committedState, ...rest } = message.payload;
|
const {
|
||||||
|
actionsById,
|
||||||
|
computedStates,
|
||||||
|
committedState,
|
||||||
|
...rest
|
||||||
|
} = message.payload;
|
||||||
message.payload = rest;
|
message.payload = rest;
|
||||||
message.actionsById = stringify(actionsById, serializeAction);
|
message.actionsById = stringify(actionsById, serializeAction);
|
||||||
message.computedStates = stringify(computedStates, serializeState);
|
message.computedStates = stringify(computedStates, serializeState);
|
||||||
|
@ -125,7 +158,10 @@ export function toContentScript(message, serializeState, serializeAction) {
|
||||||
} else if (message.type === 'EXPORT') {
|
} else if (message.type === 'EXPORT') {
|
||||||
message.payload = stringify(message.payload, serializeAction);
|
message.payload = stringify(message.payload, serializeAction);
|
||||||
if (typeof message.committedState !== 'undefined') {
|
if (typeof message.committedState !== 'undefined') {
|
||||||
message.committedState = stringify(message.committedState, serializeState);
|
message.committedState = stringify(
|
||||||
|
message.committedState,
|
||||||
|
serializeState
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post(message);
|
post(message);
|
||||||
|
@ -145,19 +181,23 @@ export function sendMessage(action, state, config, instanceId, name) {
|
||||||
maxAge: config.maxAge,
|
maxAge: config.maxAge,
|
||||||
source,
|
source,
|
||||||
name: config.name || name,
|
name: config.name || name,
|
||||||
instanceId: config.instanceId || instanceId || 1
|
instanceId: config.instanceId || instanceId || 1,
|
||||||
};
|
};
|
||||||
toContentScript(message, config.serialize, config.serialize);
|
toContentScript(message, config.serialize, config.serialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessages(event) {
|
function handleMessages(event) {
|
||||||
if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window)) return;
|
if (process.env.BABEL_ENV !== 'test' && (!event || event.source !== window))
|
||||||
|
return;
|
||||||
const message = event.data;
|
const message = event.data;
|
||||||
if (!message || message.source !== '@devtools-extension') return;
|
if (!message || message.source !== '@devtools-extension') return;
|
||||||
Object.keys(listeners).forEach(id => {
|
Object.keys(listeners).forEach((id) => {
|
||||||
if (message.id && id !== message.id) return;
|
if (message.id && id !== message.id) return;
|
||||||
if (typeof listeners[id] === 'function') listeners[id](message);
|
if (typeof listeners[id] === 'function') listeners[id](message);
|
||||||
else listeners[id].forEach(fn => { fn(message); });
|
else
|
||||||
|
listeners[id].forEach((fn) => {
|
||||||
|
fn(message);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,13 +206,13 @@ export function setListener(onMessage, instanceId) {
|
||||||
window.addEventListener('message', handleMessages, false);
|
window.addEventListener('message', handleMessages, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const liftListener = (listener, config) => message => {
|
const liftListener = (listener, config) => (message) => {
|
||||||
let data = {};
|
let data = {};
|
||||||
if (message.type === 'IMPORT') {
|
if (message.type === 'IMPORT') {
|
||||||
data.type = 'DISPATCH';
|
data.type = 'DISPATCH';
|
||||||
data.payload = {
|
data.payload = {
|
||||||
type: 'IMPORT_STATE',
|
type: 'IMPORT_STATE',
|
||||||
...importState(message.state, config)
|
...importState(message.state, config),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
data = message;
|
data = message;
|
||||||
|
@ -189,7 +229,9 @@ export function connect(preConfig) {
|
||||||
const config = preConfig || {};
|
const config = preConfig || {};
|
||||||
const id = generateId(config.instanceId);
|
const id = generateId(config.instanceId);
|
||||||
if (!config.instanceId) config.instanceId = id;
|
if (!config.instanceId) config.instanceId = id;
|
||||||
if (!config.name) config.name = document.title && id === 1 ? document.title : `Instance ${id}`;
|
if (!config.name)
|
||||||
|
config.name =
|
||||||
|
document.title && id === 1 ? document.title : `Instance ${id}`;
|
||||||
if (config.serialize) config.serialize = getSeralizeParameter(config);
|
if (config.serialize) config.serialize = getSeralizeParameter(config);
|
||||||
const actionCreators = config.actionCreators || {};
|
const actionCreators = config.actionCreators || {};
|
||||||
const latency = config.latency;
|
const latency = config.latency;
|
||||||
|
@ -200,7 +242,7 @@ export function connect(preConfig) {
|
||||||
let delayedActions = [];
|
let delayedActions = [];
|
||||||
let delayedStates = [];
|
let delayedStates = [];
|
||||||
|
|
||||||
const rootListiner = action => {
|
const rootListiner = (action) => {
|
||||||
if (autoPause) {
|
if (autoPause) {
|
||||||
if (action.type === 'START') isPaused = false;
|
if (action.type === 'START') isPaused = false;
|
||||||
else if (action.type === 'STOP') isPaused = true;
|
else if (action.type === 'STOP') isPaused = true;
|
||||||
|
@ -213,7 +255,7 @@ export function connect(preConfig) {
|
||||||
type: 'LIFTED',
|
type: 'LIFTED',
|
||||||
liftedState: { isPaused },
|
liftedState: { isPaused },
|
||||||
instanceId: id,
|
instanceId: id,
|
||||||
source
|
source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,20 +285,29 @@ export function connect(preConfig) {
|
||||||
}, latency);
|
}, latency);
|
||||||
|
|
||||||
const send = (action, state) => {
|
const send = (action, state) => {
|
||||||
if (isPaused || isFiltered(action, localFilter) || predicate && !predicate(state, action)) {
|
if (
|
||||||
|
isPaused ||
|
||||||
|
isFiltered(action, localFilter) ||
|
||||||
|
(predicate && !predicate(state, action))
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let amendedAction = action;
|
let amendedAction = action;
|
||||||
const amendedState = config.stateSanitizer ? config.stateSanitizer(state) : state;
|
const amendedState = config.stateSanitizer
|
||||||
|
? config.stateSanitizer(state)
|
||||||
|
: state;
|
||||||
if (action) {
|
if (action) {
|
||||||
if (config.getActionType) {
|
if (config.getActionType) {
|
||||||
amendedAction = config.getActionType(action);
|
amendedAction = config.getActionType(action);
|
||||||
if (typeof amendedAction !== 'object') {
|
if (typeof amendedAction !== 'object') {
|
||||||
amendedAction = { action: { type: amendedAction }, timestamp: Date.now() };
|
amendedAction = {
|
||||||
|
action: { type: amendedAction },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
} else if (config.actionSanitizer)
|
||||||
else if (config.actionSanitizer) amendedAction = config.actionSanitizer(action);
|
amendedAction = config.actionSanitizer(action);
|
||||||
amendedAction = amendActionType(amendedAction, config, send);
|
amendedAction = amendActionType(amendedAction, config, send);
|
||||||
if (latency) {
|
if (latency) {
|
||||||
delayedActions.push(amendedAction);
|
delayedActions.push(amendedAction);
|
||||||
|
@ -273,9 +324,10 @@ export function connect(preConfig) {
|
||||||
type: 'INIT',
|
type: 'INIT',
|
||||||
payload: stringify(state, config.serialize),
|
payload: stringify(state, config.serialize),
|
||||||
instanceId: id,
|
instanceId: id,
|
||||||
source
|
source,
|
||||||
};
|
};
|
||||||
if (liftedData && Array.isArray(liftedData)) { // Legacy
|
if (liftedData && Array.isArray(liftedData)) {
|
||||||
|
// Legacy
|
||||||
message.action = stringify(liftedData);
|
message.action = stringify(liftedData);
|
||||||
message.name = config.name;
|
message.name = config.name;
|
||||||
} else {
|
} else {
|
||||||
|
@ -288,7 +340,7 @@ export function connect(preConfig) {
|
||||||
name: config.name || document.title,
|
name: config.name || document.title,
|
||||||
features: config.features,
|
features: config.features,
|
||||||
serialize: !!config.serialize,
|
serialize: !!config.serialize,
|
||||||
type: config.type
|
type: config.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
post(message);
|
post(message);
|
||||||
|
@ -307,16 +359,18 @@ export function connect(preConfig) {
|
||||||
subscribe,
|
subscribe,
|
||||||
unsubscribe,
|
unsubscribe,
|
||||||
send,
|
send,
|
||||||
error
|
error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateStore(stores) {
|
export function updateStore(stores) {
|
||||||
return function (newStore, instanceId) {
|
return function (newStore, instanceId) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.warn('`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' +
|
console.warn(
|
||||||
'`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead of the extension\'s store enhancer: ' +
|
'`__REDUX_DEVTOOLS_EXTENSION__.updateStore` is deprecated, remove it and just use ' +
|
||||||
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup');
|
"`__REDUX_DEVTOOLS_EXTENSION_COMPOSE__` instead of the extension's store enhancer: " +
|
||||||
|
'https://github.com/zalmoxisus/redux-devtools-extension#12-advanced-store-setup'
|
||||||
|
);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
const store = stores[instanceId || Object.keys(stores)[0]];
|
const store = stores[instanceId || Object.keys(stores)[0]];
|
||||||
// Mutate the store in order to keep the reference
|
// Mutate the store in order to keep the reference
|
||||||
|
|
|
@ -20,19 +20,24 @@ const nextErrorTimeout = createExpBackoffTimer(5000);
|
||||||
|
|
||||||
function postError(message) {
|
function postError(message) {
|
||||||
if (handleError && !handleError()) return;
|
if (handleError && !handleError()) return;
|
||||||
window.postMessage({
|
window.postMessage(
|
||||||
|
{
|
||||||
source: '@devtools-page',
|
source: '@devtools-page',
|
||||||
type: 'ERROR',
|
type: 'ERROR',
|
||||||
message: message
|
message: message,
|
||||||
}, '*');
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function catchErrors(e) {
|
function catchErrors(e) {
|
||||||
if (
|
if (
|
||||||
window.devToolsOptions && !window.devToolsOptions.shouldCatchErrors
|
(window.devToolsOptions && !window.devToolsOptions.shouldCatchErrors) ||
|
||||||
|| e.timeStamp - lastTime < nextErrorTimeout()
|
e.timeStamp - lastTime < nextErrorTimeout()
|
||||||
) return;
|
)
|
||||||
lastTime = e.timeStamp; nextErrorTimeout(true);
|
return;
|
||||||
|
lastTime = e.timeStamp;
|
||||||
|
nextErrorTimeout(true);
|
||||||
postError(e.message);
|
postError(e.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
export default function openWindow(position) {
|
export default function openWindow(position) {
|
||||||
window.postMessage({
|
window.postMessage(
|
||||||
|
{
|
||||||
source: '@devtools-page',
|
source: '@devtools-page',
|
||||||
type: 'OPEN',
|
type: 'OPEN',
|
||||||
position: position || 'right'
|
position: position || 'right',
|
||||||
}, '*');
|
},
|
||||||
|
'*'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,13 +41,25 @@ class App extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
monitor, position, togglePersist,
|
monitor,
|
||||||
dispatcherIsOpen, sliderIsOpen, options, liftedState
|
position,
|
||||||
|
togglePersist,
|
||||||
|
dispatcherIsOpen,
|
||||||
|
sliderIsOpen,
|
||||||
|
options,
|
||||||
|
liftedState,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (!position && (!options || !options.features)) {
|
if (!position && (!options || !options.features)) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px', width: '100%', textAlign: 'center' }}>
|
<div style={{ padding: '20px', width: '100%', textAlign: 'center' }}>
|
||||||
No store found. Make sure to follow <a href="https://github.com/zalmoxisus/redux-devtools-extension#usage" target="_blank">the instructions</a>.
|
No store found. Make sure to follow{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/zalmoxisus/redux-devtools-extension#usage"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
the instructions
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +78,7 @@ class App extends Component {
|
||||||
lib={options.lib || options.explicitLib}
|
lib={options.lib || options.explicitLib}
|
||||||
/>
|
/>
|
||||||
<Notification />
|
<Notification />
|
||||||
{sliderIsOpen && options.connectionId && options.features.jump &&
|
{sliderIsOpen && options.connectionId && options.features.jump && (
|
||||||
<SliderMonitor
|
<SliderMonitor
|
||||||
monitor="SliderMonitor"
|
monitor="SliderMonitor"
|
||||||
liftedState={liftedState}
|
liftedState={liftedState}
|
||||||
|
@ -77,68 +89,67 @@ class App extends Component {
|
||||||
style={{ padding: '15px 5px' }}
|
style={{ padding: '15px 5px' }}
|
||||||
fillColor="rgb(120, 144, 156)"
|
fillColor="rgb(120, 144, 156)"
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{dispatcherIsOpen && options.connectionId && options.features.dispatch &&
|
{dispatcherIsOpen &&
|
||||||
<Dispatcher options={options} />
|
options.connectionId &&
|
||||||
}
|
options.features.dispatch && <Dispatcher options={options} />}
|
||||||
<div style={styles.buttonBar}>
|
<div style={styles.buttonBar}>
|
||||||
{!window.isElectron && position !== '#left' &&
|
{!window.isElectron && position !== '#left' && (
|
||||||
<Button
|
<Button
|
||||||
Icon={LeftIcon}
|
Icon={LeftIcon}
|
||||||
onClick={() => { this.openWindow('left'); }}
|
onClick={() => {
|
||||||
|
this.openWindow('left');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{!window.isElectron && position !== '#right' &&
|
{!window.isElectron && position !== '#right' && (
|
||||||
<Button
|
<Button
|
||||||
Icon={RightIcon}
|
Icon={RightIcon}
|
||||||
onClick={() => { this.openWindow('right'); }}
|
onClick={() => {
|
||||||
|
this.openWindow('right');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{!window.isElectron && position !== '#bottom' &&
|
{!window.isElectron && position !== '#bottom' && (
|
||||||
<Button
|
<Button
|
||||||
Icon={BottomIcon}
|
Icon={BottomIcon}
|
||||||
onClick={() => { this.openWindow('bottom'); }}
|
onClick={() => {
|
||||||
|
this.openWindow('bottom');
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{features.pause &&
|
{features.pause && <RecordButton paused={liftedState.isPaused} />}
|
||||||
<RecordButton paused={liftedState.isPaused} />
|
{features.lock && <LockButton locked={liftedState.isLocked} />}
|
||||||
}
|
{features.persist && (
|
||||||
{features.lock &&
|
<Button Icon={PersistIcon} onClick={togglePersist}>
|
||||||
<LockButton locked={liftedState.isLocked} />
|
Persist
|
||||||
}
|
</Button>
|
||||||
{features.persist &&
|
)}
|
||||||
<Button
|
{features.dispatch && (
|
||||||
Icon={PersistIcon}
|
|
||||||
onClick={togglePersist}
|
|
||||||
>Persist</Button>
|
|
||||||
}
|
|
||||||
{features.dispatch &&
|
|
||||||
<DispatcherButton dispatcherIsOpen={dispatcherIsOpen} />
|
<DispatcherButton dispatcherIsOpen={dispatcherIsOpen} />
|
||||||
}
|
)}
|
||||||
{features.jump &&
|
{features.jump && <SliderButton isOpen={sliderIsOpen} />}
|
||||||
<SliderButton isOpen={sliderIsOpen}/>
|
{features.import && <ImportButton />}
|
||||||
}
|
{features.export && <ExportButton />}
|
||||||
{features.import &&
|
{position &&
|
||||||
<ImportButton />
|
(position !== '#popup' ||
|
||||||
}
|
navigator.userAgent.indexOf('Firefox') !== -1) && <PrintButton />}
|
||||||
{features.export &&
|
{!window.isElectron && (
|
||||||
<ExportButton />
|
|
||||||
}
|
|
||||||
{position && (position !== '#popup' || navigator.userAgent.indexOf('Firefox') !== -1) &&
|
|
||||||
<PrintButton />
|
|
||||||
}
|
|
||||||
{!window.isElectron &&
|
|
||||||
<Button
|
<Button
|
||||||
Icon={RemoteIcon}
|
Icon={RemoteIcon}
|
||||||
onClick={() => { this.openWindow('remote'); }}
|
onClick={() => {
|
||||||
>Remote</Button>
|
this.openWindow('remote');
|
||||||
}
|
}}
|
||||||
{(chrome.runtime.openOptionsPage || navigator.userAgent.indexOf('Firefox') !== -1) &&
|
>
|
||||||
<Button
|
Remote
|
||||||
Icon={SettingsIcon}
|
</Button>
|
||||||
onClick={this.openOptionsPage}
|
)}
|
||||||
>Settings</Button>
|
{(chrome.runtime.openOptionsPage ||
|
||||||
}
|
navigator.userAgent.indexOf('Firefox') !== -1) && (
|
||||||
|
<Button Icon={SettingsIcon} onClick={this.openOptionsPage}>
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -158,7 +169,7 @@ App.propTypes = {
|
||||||
position: PropTypes.string,
|
position: PropTypes.string,
|
||||||
reports: PropTypes.array.isRequired,
|
reports: PropTypes.array.isRequired,
|
||||||
dispatcherIsOpen: PropTypes.bool,
|
dispatcherIsOpen: PropTypes.bool,
|
||||||
sliderIsOpen: PropTypes.bool
|
sliderIsOpen: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
@ -173,7 +184,7 @@ function mapStateToProps(state) {
|
||||||
dispatcherIsOpen: state.monitor.dispatcherIsOpen,
|
dispatcherIsOpen: state.monitor.dispatcherIsOpen,
|
||||||
sliderIsOpen: state.monitor.sliderIsOpen,
|
sliderIsOpen: state.monitor.sliderIsOpen,
|
||||||
reports: state.reports.data,
|
reports: state.reports.data,
|
||||||
shouldSync: state.instances.sync
|
shouldSync: state.instances.sync,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +192,9 @@ function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
liftedDispatch: bindActionCreators(liftedDispatch, dispatch),
|
liftedDispatch: bindActionCreators(liftedDispatch, dispatch),
|
||||||
getReport: bindActionCreators(getReport, dispatch),
|
getReport: bindActionCreators(getReport, dispatch),
|
||||||
togglePersist: () => { dispatch({ type: 'TOGGLE_PERSIST' }); }
|
togglePersist: () => {
|
||||||
|
dispatch({ type: 'TOGGLE_PERSIST' });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import stringifyJSON from 'remotedev-app/lib/utils/stringifyJSON';
|
import stringifyJSON from 'remotedev-app/lib/utils/stringifyJSON';
|
||||||
import { UPDATE_STATE, REMOVE_INSTANCE, LIFTED_ACTION } from 'remotedev-app/lib/constants/actionTypes';
|
import {
|
||||||
|
UPDATE_STATE,
|
||||||
|
REMOVE_INSTANCE,
|
||||||
|
LIFTED_ACTION,
|
||||||
|
} from 'remotedev-app/lib/constants/actionTypes';
|
||||||
import { nonReduxDispatch } from 'remotedev-app/lib/utils/monitorActions';
|
import { nonReduxDispatch } from 'remotedev-app/lib/utils/monitorActions';
|
||||||
import syncOptions from '../../browser/extension/options/syncOptions';
|
import syncOptions from '../../browser/extension/options/syncOptions';
|
||||||
import openDevToolsWindow from '../../browser/extension/background/openWindow';
|
import openDevToolsWindow from '../../browser/extension/background/openWindow';
|
||||||
|
@ -10,21 +14,22 @@ const DISCONNECTED = 'socket/DISCONNECTED';
|
||||||
const connections = {
|
const connections = {
|
||||||
tab: {},
|
tab: {},
|
||||||
panel: {},
|
panel: {},
|
||||||
monitor: {}
|
monitor: {},
|
||||||
};
|
};
|
||||||
const chunks = {};
|
const chunks = {};
|
||||||
let monitors = 0;
|
let monitors = 0;
|
||||||
let isMonitored = false;
|
let isMonitored = false;
|
||||||
|
|
||||||
const getId = (sender, name) => sender.tab ? sender.tab.id : name || sender.id;
|
const getId = (sender, name) =>
|
||||||
|
sender.tab ? sender.tab.id : name || sender.id;
|
||||||
|
|
||||||
function toMonitors(action, tabId, verbose) {
|
function toMonitors(action, tabId, verbose) {
|
||||||
Object.keys(connections.monitor).forEach(id => {
|
Object.keys(connections.monitor).forEach((id) => {
|
||||||
connections.monitor[id].postMessage(
|
connections.monitor[id].postMessage(
|
||||||
verbose || action.type === 'ERROR' ? action : { type: UPDATE_STATE }
|
verbose || action.type === 'ERROR' ? action : { type: UPDATE_STATE }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
Object.keys(connections.panel).forEach(id => {
|
Object.keys(connections.panel).forEach((id) => {
|
||||||
connections.panel[id].postMessage(action);
|
connections.panel[id].postMessage(action);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -34,13 +39,13 @@ function toContentScript({ message, action, id, instanceId, state }) {
|
||||||
type: message,
|
type: message,
|
||||||
action,
|
action,
|
||||||
state: nonReduxDispatch(window.store, message, instanceId, action, state),
|
state: nonReduxDispatch(window.store, message, instanceId, action, state),
|
||||||
id: instanceId.toString().replace(/^[^\/]+\//, '')
|
id: instanceId.toString().replace(/^[^\/]+\//, ''),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAllTabs(msg) {
|
function toAllTabs(msg) {
|
||||||
const tabs = connections.tab;
|
const tabs = connections.tab;
|
||||||
Object.keys(tabs).forEach(id => {
|
Object.keys(tabs).forEach((id) => {
|
||||||
tabs[id].postMessage(msg);
|
tabs[id].postMessage(msg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -67,7 +72,7 @@ function getReducerError() {
|
||||||
function togglePersist() {
|
function togglePersist() {
|
||||||
const state = window.store.getState();
|
const state = window.store.getState();
|
||||||
if (state.persistStates) {
|
if (state.persistStates) {
|
||||||
Object.keys(state.instances.connections).forEach(id => {
|
Object.keys(state.instances.connections).forEach((id) => {
|
||||||
if (connections.tab[id]) return;
|
if (connections.tab[id]) return;
|
||||||
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
window.store.dispatch({ type: REMOVE_INSTANCE, id });
|
||||||
toMonitors({ type: 'NA', id });
|
toMonitors({ type: 'NA', id });
|
||||||
|
@ -92,7 +97,7 @@ function messaging(request, sender, sendResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (request.type === 'GET_OPTIONS') {
|
if (request.type === 'GET_OPTIONS') {
|
||||||
window.syncOptions.get(options => {
|
window.syncOptions.get((options) => {
|
||||||
sendResponse({ options });
|
sendResponse({ options });
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -103,7 +108,11 @@ function messaging(request, sender, sendResponse) {
|
||||||
}
|
}
|
||||||
if (request.type === 'OPEN') {
|
if (request.type === 'OPEN') {
|
||||||
let position = 'devtools-left';
|
let position = 'devtools-left';
|
||||||
if (['remote', 'panel', 'left', 'right', 'bottom'].indexOf(request.position) !== -1) {
|
if (
|
||||||
|
['remote', 'panel', 'left', 'right', 'bottom'].indexOf(
|
||||||
|
request.position
|
||||||
|
) !== -1
|
||||||
|
) {
|
||||||
position = 'devtools-' + request.position;
|
position = 'devtools-' + request.position;
|
||||||
}
|
}
|
||||||
openDevToolsWindow(position);
|
openDevToolsWindow(position);
|
||||||
|
@ -118,10 +127,12 @@ function messaging(request, sender, sendResponse) {
|
||||||
const reducerError = getReducerError();
|
const reducerError = getReducerError();
|
||||||
chrome.notifications.create('app-error', {
|
chrome.notifications.create('app-error', {
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
title: reducerError ? 'An error occurred in the reducer' : 'An error occurred in the app',
|
title: reducerError
|
||||||
|
? 'An error occurred in the reducer'
|
||||||
|
: 'An error occurred in the app',
|
||||||
message: reducerError || request.message,
|
message: reducerError || request.message,
|
||||||
iconUrl: 'img/logo/48x48.png',
|
iconUrl: 'img/logo/48x48.png',
|
||||||
isClickable: !!reducerError
|
isClickable: !!reducerError,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -134,7 +145,8 @@ function messaging(request, sender, sendResponse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (request.split === 'chunk') {
|
if (request.split === 'chunk') {
|
||||||
chunks[instanceId][request.chunk[0]] = (chunks[instanceId][request.chunk[0]] || '') + request.chunk[1];
|
chunks[instanceId][request.chunk[0]] =
|
||||||
|
(chunks[instanceId][request.chunk[0]] || '') + request.chunk[1];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action.request = chunks[instanceId];
|
action.request = chunks[instanceId];
|
||||||
|
@ -180,7 +192,7 @@ function onConnect(port) {
|
||||||
id = getId(port.sender);
|
id = getId(port.sender);
|
||||||
if (port.sender.frameId) id = `${id}-${port.sender.frameId}`;
|
if (port.sender.frameId) id = `${id}-${port.sender.frameId}`;
|
||||||
connections.tab[id] = port;
|
connections.tab[id] = port;
|
||||||
listener = msg => {
|
listener = (msg) => {
|
||||||
if (msg.name === 'INIT_INSTANCE') {
|
if (msg.name === 'INIT_INSTANCE') {
|
||||||
if (typeof id === 'number') {
|
if (typeof id === 'number') {
|
||||||
chrome.pageAction.show(id);
|
chrome.pageAction.show(id);
|
||||||
|
@ -195,8 +207,12 @@ function onConnect(port) {
|
||||||
if (!persistedState) return;
|
if (!persistedState) return;
|
||||||
toContentScript({
|
toContentScript({
|
||||||
message: 'IMPORT',
|
message: 'IMPORT',
|
||||||
id, instanceId,
|
id,
|
||||||
state: stringifyJSON(persistedState, state.instances.options[instanceId].serialize)
|
instanceId,
|
||||||
|
state: stringifyJSON(
|
||||||
|
persistedState,
|
||||||
|
state.instances.options[instanceId].serialize
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -213,12 +229,13 @@ function onConnect(port) {
|
||||||
monitorInstances(true);
|
monitorInstances(true);
|
||||||
monitors++;
|
monitors++;
|
||||||
port.onDisconnect.addListener(disconnect('monitor', id));
|
port.onDisconnect.addListener(disconnect('monitor', id));
|
||||||
} else { // devpanel
|
} else {
|
||||||
|
// devpanel
|
||||||
id = port.name || port.sender.frameId;
|
id = port.name || port.sender.frameId;
|
||||||
connections.panel[id] = port;
|
connections.panel[id] = port;
|
||||||
monitorInstances(true, port.name);
|
monitorInstances(true, port.name);
|
||||||
monitors++;
|
monitors++;
|
||||||
listener = msg => {
|
listener = (msg) => {
|
||||||
window.store.dispatch(msg);
|
window.store.dispatch(msg);
|
||||||
};
|
};
|
||||||
port.onMessage.addListener(listener);
|
port.onMessage.addListener(listener);
|
||||||
|
@ -231,7 +248,7 @@ chrome.runtime.onConnectExternal.addListener(onConnect);
|
||||||
chrome.runtime.onMessage.addListener(messaging);
|
chrome.runtime.onMessage.addListener(messaging);
|
||||||
chrome.runtime.onMessageExternal.addListener(messaging);
|
chrome.runtime.onMessageExternal.addListener(messaging);
|
||||||
|
|
||||||
chrome.notifications.onClicked.addListener(id => {
|
chrome.notifications.onClicked.addListener((id) => {
|
||||||
chrome.notifications.clear(id);
|
chrome.notifications.clear(id);
|
||||||
openDevToolsWindow('devtools-right');
|
openDevToolsWindow('devtools-right');
|
||||||
});
|
});
|
||||||
|
@ -239,7 +256,7 @@ chrome.notifications.onClicked.addListener(id => {
|
||||||
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
|
window.syncOptions = syncOptions(toAllTabs); // Expose to the options page
|
||||||
|
|
||||||
export default function api() {
|
export default function api() {
|
||||||
return next => action => {
|
return (next) => (action) => {
|
||||||
if (action.type === LIFTED_ACTION) toContentScript(action);
|
if (action.type === LIFTED_ACTION) toContentScript(action);
|
||||||
else if (action.type === 'TOGGLE_PERSIST') togglePersist();
|
else if (action.type === 'TOGGLE_PERSIST') togglePersist();
|
||||||
return next(action);
|
return next(action);
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { SELECT_INSTANCE, UPDATE_STATE } from 'remotedev-app/lib/constants/actionTypes';
|
import {
|
||||||
|
SELECT_INSTANCE,
|
||||||
|
UPDATE_STATE,
|
||||||
|
} from 'remotedev-app/lib/constants/actionTypes';
|
||||||
|
|
||||||
function selectInstance(tabId, store, next) {
|
function selectInstance(tabId, store, next) {
|
||||||
const instances = store.getState().instances;
|
const instances = store.getState().instances;
|
||||||
|
@ -10,24 +13,27 @@ function selectInstance(tabId, store, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentTabId(next) {
|
function getCurrentTabId(next) {
|
||||||
chrome.tabs.query({
|
chrome.tabs.query(
|
||||||
|
{
|
||||||
active: true,
|
active: true,
|
||||||
lastFocusedWindow: true
|
lastFocusedWindow: true,
|
||||||
}, tabs => {
|
},
|
||||||
|
(tabs) => {
|
||||||
const tab = tabs[0];
|
const tab = tabs[0];
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
next(tab.id);
|
next(tab.id);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function popupSelector(store) {
|
export default function popupSelector(store) {
|
||||||
return next => action => {
|
return (next) => (action) => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
if (action.type === UPDATE_STATE) {
|
if (action.type === UPDATE_STATE) {
|
||||||
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
if (chrome.devtools && chrome.devtools.inspectedWindow) {
|
||||||
selectInstance(chrome.devtools.inspectedWindow.tabId, store, next);
|
selectInstance(chrome.devtools.inspectedWindow.tabId, store, next);
|
||||||
} else {
|
} else {
|
||||||
getCurrentTabId(tabId => selectInstance(tabId, store, next));
|
getCurrentTabId((tabId) => selectInstance(tabId, store, next));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { LIFTED_ACTION, UPDATE_STATE, SELECT_INSTANCE } from 'remotedev-app/lib/constants/actionTypes';
|
import {
|
||||||
|
LIFTED_ACTION,
|
||||||
|
UPDATE_STATE,
|
||||||
|
SELECT_INSTANCE,
|
||||||
|
} from 'remotedev-app/lib/constants/actionTypes';
|
||||||
import { getActiveInstance } from 'remotedev-app/lib/reducers/instances';
|
import { getActiveInstance } from 'remotedev-app/lib/reducers/instances';
|
||||||
|
|
||||||
function panelDispatcher(bgConnection) {
|
function panelDispatcher(bgConnection) {
|
||||||
let autoselected = false;
|
let autoselected = false;
|
||||||
const tabId = chrome.devtools.inspectedWindow.tabId;
|
const tabId = chrome.devtools.inspectedWindow.tabId;
|
||||||
|
|
||||||
return store => next => action => {
|
return (store) => (next) => (action) => {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
if (!autoselected && action.type === UPDATE_STATE && tabId) {
|
if (!autoselected && action.type === UPDATE_STATE && tabId) {
|
||||||
autoselected = true;
|
autoselected = true;
|
||||||
const connections = store.getState()
|
const connections = store.getState().instances.connections[tabId];
|
||||||
.instances.connections[tabId];
|
|
||||||
if (connections && connections.length === 1) {
|
if (connections && connections.length === 1) {
|
||||||
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
next({ type: SELECT_INSTANCE, selected: connections[0] });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import { UPDATE_STATE, LIFTED_ACTION } from 'remotedev-app/lib/constants/actionTypes';
|
import {
|
||||||
|
UPDATE_STATE,
|
||||||
|
LIFTED_ACTION,
|
||||||
|
} from 'remotedev-app/lib/constants/actionTypes';
|
||||||
import { getActiveInstance } from 'remotedev-app/lib/reducers/instances';
|
import { getActiveInstance } from 'remotedev-app/lib/reducers/instances';
|
||||||
|
|
||||||
const syncStores = baseStore => store => next => action => {
|
const syncStores = (baseStore) => (store) => (next) => (action) => {
|
||||||
if (action.type === UPDATE_STATE) {
|
if (action.type === UPDATE_STATE) {
|
||||||
return next({
|
return next({
|
||||||
...action,
|
...action,
|
||||||
instances: baseStore.getState().instances
|
instances: baseStore.getState().instances,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (action.type === LIFTED_ACTION || action.type === 'TOGGLE_PERSIST') {
|
if (action.type === LIFTED_ACTION || action.type === 'TOGGLE_PERSIST') {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import persistStates from './persistStates';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
instances,
|
instances,
|
||||||
persistStates
|
persistStates,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -10,7 +10,7 @@ const rootReducer = combineReducers({
|
||||||
monitor,
|
monitor,
|
||||||
test,
|
test,
|
||||||
reports,
|
reports,
|
||||||
notification
|
notification,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -12,7 +12,7 @@ const rootReducer = combineReducers({
|
||||||
test,
|
test,
|
||||||
socket,
|
socket,
|
||||||
reports,
|
reports,
|
||||||
notification
|
notification,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default rootReducer;
|
export default rootReducer;
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { initialState, dispatchAction } from 'remotedev-app/lib/reducers/instances';
|
import {
|
||||||
import { UPDATE_STATE, SELECT_INSTANCE, LIFTED_ACTION } from 'remotedev-app/lib/constants/actionTypes';
|
initialState,
|
||||||
|
dispatchAction,
|
||||||
|
} from 'remotedev-app/lib/reducers/instances';
|
||||||
|
import {
|
||||||
|
UPDATE_STATE,
|
||||||
|
SELECT_INSTANCE,
|
||||||
|
LIFTED_ACTION,
|
||||||
|
} from 'remotedev-app/lib/constants/actionTypes';
|
||||||
|
|
||||||
export default function instances(state = initialState, action) {
|
export default function instances(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
|
@ -23,13 +23,16 @@ export default class Monitor {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
clearTimeout(this.waitingTimeout);
|
clearTimeout(this.waitingTimeout);
|
||||||
};
|
};
|
||||||
isHotReloaded = () => this.lastAction && /^@@redux\/(INIT|REPLACE)/.test(this.lastAction);
|
isHotReloaded = () =>
|
||||||
isMonitorAction = () => this.lastAction && this.lastAction !== 'PERFORM_ACTION';
|
this.lastAction && /^@@redux\/(INIT|REPLACE)/.test(this.lastAction);
|
||||||
|
isMonitorAction = () =>
|
||||||
|
this.lastAction && this.lastAction !== 'PERFORM_ACTION';
|
||||||
isTimeTraveling = () => this.lastAction === 'JUMP_TO_STATE';
|
isTimeTraveling = () => this.lastAction === 'JUMP_TO_STATE';
|
||||||
isPaused = () => {
|
isPaused = () => {
|
||||||
if (this.paused) {
|
if (this.paused) {
|
||||||
if (this.lastAction !== 'BLOCKED') {
|
if (this.lastAction !== 'BLOCKED') {
|
||||||
if (!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__) this.lastAction = 'BLOCKED';
|
if (!window.__REDUX_DEVTOOLS_EXTENSION_LOCKED__)
|
||||||
|
this.lastAction = 'BLOCKED';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -3,15 +3,15 @@ import instrument from 'redux-devtools-instrument';
|
||||||
import persistState from 'redux-devtools/lib/persistState';
|
import persistState from 'redux-devtools/lib/persistState';
|
||||||
|
|
||||||
export function getUrlParam(key) {
|
export function getUrlParam(key) {
|
||||||
const matches = window.location.href.match(new RegExp(`[?&]${key}=([^&#]+)\\b`));
|
const matches = window.location.href.match(
|
||||||
return (matches && matches.length > 0) ? matches[1] : null;
|
new RegExp(`[?&]${key}=([^&#]+)\\b`)
|
||||||
|
);
|
||||||
|
return matches && matches.length > 0 ? matches[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function configureStore(next, monitorReducer, config) {
|
export default function configureStore(next, monitorReducer, config) {
|
||||||
return compose(
|
return compose(
|
||||||
instrument(
|
instrument(monitorReducer, {
|
||||||
monitorReducer,
|
|
||||||
{
|
|
||||||
maxAge: config.maxAge,
|
maxAge: config.maxAge,
|
||||||
trace: config.trace,
|
trace: config.trace,
|
||||||
traceLimit: config.traceLimit,
|
traceLimit: config.traceLimit,
|
||||||
|
@ -19,9 +19,8 @@ export default function configureStore(next, monitorReducer, config) {
|
||||||
shouldHotReload: config.shouldHotReload,
|
shouldHotReload: config.shouldHotReload,
|
||||||
shouldRecordChanges: config.shouldRecordChanges,
|
shouldRecordChanges: config.shouldRecordChanges,
|
||||||
shouldStartLocked: config.shouldStartLocked,
|
shouldStartLocked: config.shouldStartLocked,
|
||||||
pauseActionType: config.pauseActionType || '@@PAUSED'
|
pauseActionType: config.pauseActionType || '@@PAUSED',
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
persistState(
|
persistState(
|
||||||
getUrlParam('debug_session'),
|
getUrlParam('debug_session'),
|
||||||
config.deserializeState,
|
config.deserializeState,
|
||||||
|
|
|
@ -5,6 +5,10 @@ import panelDispatcher from '../middlewares/panelSync';
|
||||||
import rootReducer from '../reducers/panel';
|
import rootReducer from '../reducers/panel';
|
||||||
|
|
||||||
export default function configureStore(position, bgConnection, preloadedState) {
|
export default function configureStore(position, bgConnection, preloadedState) {
|
||||||
const enhancer = applyMiddleware(exportState, panelDispatcher(bgConnection), persist(position));
|
const enhancer = applyMiddleware(
|
||||||
|
exportState,
|
||||||
|
panelDispatcher(bgConnection),
|
||||||
|
persist(position)
|
||||||
|
);
|
||||||
return createStore(rootReducer, preloadedState, enhancer);
|
return createStore(rootReducer, preloadedState, enhancer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,14 @@ import rootReducer from '../reducers/window';
|
||||||
|
|
||||||
export default function configureStore(baseStore, position, preloadedState) {
|
export default function configureStore(baseStore, position, preloadedState) {
|
||||||
let enhancer;
|
let enhancer;
|
||||||
const middlewares = [exportState, api, syncStores(baseStore), persist(position)];
|
const middlewares = [
|
||||||
if (!position || position === '#popup') { // select current tab instance for devPanel and pageAction
|
exportState,
|
||||||
|
api,
|
||||||
|
syncStores(baseStore),
|
||||||
|
persist(position),
|
||||||
|
];
|
||||||
|
if (!position || position === '#popup') {
|
||||||
|
// select current tab instance for devPanel and pageAction
|
||||||
middlewares.push(instanceSelector);
|
middlewares.push(instanceSelector);
|
||||||
}
|
}
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
@ -18,16 +24,22 @@ export default function configureStore(baseStore, position, preloadedState) {
|
||||||
} else {
|
} else {
|
||||||
enhancer = compose(
|
enhancer = compose(
|
||||||
applyMiddleware(...middlewares),
|
applyMiddleware(...middlewares),
|
||||||
window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : noop => noop
|
window.__REDUX_DEVTOOLS_EXTENSION__
|
||||||
|
? window.__REDUX_DEVTOOLS_EXTENSION__()
|
||||||
|
: (noop) => noop
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const store = createStore(rootReducer, preloadedState, enhancer);
|
const store = createStore(rootReducer, preloadedState, enhancer);
|
||||||
|
|
||||||
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], options => {
|
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], (options) => {
|
||||||
if (!options['s:hostname'] || !options['s:port']) return;
|
if (!options['s:hostname'] || !options['s:port']) return;
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: CONNECT_REQUEST,
|
type: CONNECT_REQUEST,
|
||||||
options: { hostname: options['s:hostname'], port: options['s:port'], secure: options['s:secure'] }
|
options: {
|
||||||
|
hostname: options['s:hostname'],
|
||||||
|
port: options['s:port'],
|
||||||
|
secure: options['s:secure'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,15 @@ export function createMenu() {
|
||||||
{ id: 'devtools-left', title: 'To left' },
|
{ id: 'devtools-left', title: 'To left' },
|
||||||
{ id: 'devtools-right', title: 'To right' },
|
{ id: 'devtools-right', title: 'To right' },
|
||||||
{ id: 'devtools-bottom', title: 'To bottom' },
|
{ id: 'devtools-bottom', title: 'To bottom' },
|
||||||
{ id: 'devtools-panel', title: 'Open in a panel (enable in browser settings)' },
|
{
|
||||||
{ id: 'devtools-remote', title: 'Open Remote DevTools' }
|
id: 'devtools-panel',
|
||||||
|
title: 'Open in a panel (enable in browser settings)',
|
||||||
|
},
|
||||||
|
{ id: 'devtools-remote', title: 'Open Remote DevTools' },
|
||||||
];
|
];
|
||||||
|
|
||||||
let shortcuts = {};
|
let shortcuts = {};
|
||||||
chrome.commands.getAll(commands => {
|
chrome.commands.getAll((commands) => {
|
||||||
commands.forEach(({ name, shortcut }) => {
|
commands.forEach(({ name, shortcut }) => {
|
||||||
shortcuts[name] = shortcut;
|
shortcuts[name] = shortcut;
|
||||||
});
|
});
|
||||||
|
@ -19,7 +22,7 @@ export function createMenu() {
|
||||||
chrome.contextMenus.create({
|
chrome.contextMenus.create({
|
||||||
id: id,
|
id: id,
|
||||||
title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
|
title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
|
||||||
contexts: ['all']
|
contexts: ['all'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
const getIfExists = (sel, template) => (
|
const getIfExists = (sel, template) =>
|
||||||
typeof sel === 'undefined' ||
|
typeof sel === 'undefined' ||
|
||||||
typeof template === 'undefined' ||
|
typeof template === 'undefined' ||
|
||||||
typeof template[sel] === 'undefined' ?
|
typeof template[sel] === 'undefined'
|
||||||
0 : sel
|
? 0
|
||||||
);
|
: sel;
|
||||||
|
|
||||||
export default function getPreloadedState(position, cb) {
|
export default function getPreloadedState(position, cb) {
|
||||||
chrome.storage.local.get([
|
chrome.storage.local.get(
|
||||||
'monitor' + position, 'slider' + position, 'dispatcher' + position,
|
[
|
||||||
'test-templates', 'test-templates-sel'
|
'monitor' + position,
|
||||||
], options => {
|
'slider' + position,
|
||||||
|
'dispatcher' + position,
|
||||||
|
'test-templates',
|
||||||
|
'test-templates-sel',
|
||||||
|
],
|
||||||
|
(options) => {
|
||||||
cb({
|
cb({
|
||||||
monitor: {
|
monitor: {
|
||||||
selected: options['monitor' + position],
|
selected: options['monitor' + position],
|
||||||
|
@ -17,9 +22,13 @@ export default function getPreloadedState(position, cb) {
|
||||||
dispatcherIsOpen: options['dispatcher' + position] || false,
|
dispatcherIsOpen: options['dispatcher' + position] || false,
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
selected: getIfExists(options['test-templates-sel'], options['test-templates']),
|
selected: getIfExists(
|
||||||
templates: options['test-templates']
|
options['test-templates-sel'],
|
||||||
}
|
options['test-templates']
|
||||||
});
|
),
|
||||||
|
templates: options['test-templates'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import syncOptions from '../options/syncOptions';
|
||||||
window.store = configureStore();
|
window.store = configureStore();
|
||||||
|
|
||||||
// Listen for keyboard shortcuts
|
// Listen for keyboard shortcuts
|
||||||
chrome.commands.onCommand.addListener(shortcut => {
|
chrome.commands.onCommand.addListener((shortcut) => {
|
||||||
openDevToolsWindow(shortcut);
|
openDevToolsWindow(shortcut);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the context menu when installed
|
// Create the context menu when installed
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
syncOptions().get(option => {
|
syncOptions().get((option) => {
|
||||||
if (option.showContextMenus) createMenu();
|
if (option.showContextMenus) createMenu();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create or Remove context menu when config changed
|
// Create or Remove context menu when config changed
|
||||||
chrome.storage.onChanged.addListener(changes => {
|
chrome.storage.onChanged.addListener((changes) => {
|
||||||
if (changes.showContextMenus) {
|
if (changes.showContextMenus) {
|
||||||
if (changes.showContextMenus.newValue) createMenu();
|
if (changes.showContextMenus.newValue) createMenu();
|
||||||
else removeMenu();
|
else removeMenu();
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
import { LIFTED_ACTION } from 'remotedev-app/lib/constants/actionTypes';
|
import { LIFTED_ACTION } from 'remotedev-app/lib/constants/actionTypes';
|
||||||
|
|
||||||
export function getReport(reportId, tabId, instanceId) {
|
export function getReport(reportId, tabId, instanceId) {
|
||||||
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], options => {
|
chrome.storage.local.get(['s:hostname', 's:port', 's:secure'], (options) => {
|
||||||
if (!options['s:hostname'] || !options['s:port']) return;
|
if (!options['s:hostname'] || !options['s:port']) return;
|
||||||
const url = `${options['s:secure'] ? 'https' : 'http'}://${options['s:hostname']}:${options['s:port']}`;
|
const url = `${options['s:secure'] ? 'https' : 'http'}://${
|
||||||
|
options['s:hostname']
|
||||||
|
}:${options['s:port']}`;
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json'
|
'content-type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ op: 'get', id: reportId })
|
body: JSON.stringify({ op: 'get', id: reportId }),
|
||||||
}).then(response => {
|
})
|
||||||
|
.then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then(json => {
|
})
|
||||||
|
.then((json) => {
|
||||||
const { payload, preloadedState } = json;
|
const { payload, preloadedState } = json;
|
||||||
if (!payload) return;
|
if (!payload) return;
|
||||||
window.store.dispatch({
|
window.store.dispatch({
|
||||||
|
@ -21,9 +25,10 @@ export function getReport(reportId, tabId, instanceId) {
|
||||||
message: 'IMPORT',
|
message: 'IMPORT',
|
||||||
state: JSON.stringify({ payload, preloadedState }),
|
state: JSON.stringify({ payload, preloadedState }),
|
||||||
id: tabId,
|
id: tabId,
|
||||||
instanceId: `${tabId}/${instanceId}`
|
instanceId: `${tabId}/${instanceId}`,
|
||||||
});
|
});
|
||||||
}).catch(function(err) {
|
})
|
||||||
|
.catch(function (err) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.warn(err);
|
console.warn(err);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
|
|
|
@ -9,7 +9,8 @@ export default function openDevToolsWindow(position) {
|
||||||
lastPosition = position;
|
lastPosition = position;
|
||||||
} else {
|
} else {
|
||||||
let params = { focused: true };
|
let params = { focused: true };
|
||||||
if (lastPosition !== position && position !== 'devtools-panel') params = {...params, ...customOptions};
|
if (lastPosition !== position && position !== 'devtools-panel')
|
||||||
|
params = { ...params, ...customOptions };
|
||||||
chrome.windows.update(windows[position], params, () => {
|
chrome.windows.update(windows[position], params, () => {
|
||||||
lastPosition = null;
|
lastPosition = null;
|
||||||
if (chrome.runtime.lastError) callback();
|
if (chrome.runtime.lastError) callback();
|
||||||
|
@ -20,10 +21,12 @@ export default function openDevToolsWindow(position) {
|
||||||
focusIfExist(() => {
|
focusIfExist(() => {
|
||||||
let options = {
|
let options = {
|
||||||
type: 'popup',
|
type: 'popup',
|
||||||
...customOptions
|
...customOptions,
|
||||||
};
|
};
|
||||||
if (action === 'open') {
|
if (action === 'open') {
|
||||||
options.url = chrome.extension.getURL(url + '#' + position.substr(position.indexOf('-') + 1));
|
options.url = chrome.extension.getURL(
|
||||||
|
url + '#' + position.substr(position.indexOf('-') + 1)
|
||||||
|
);
|
||||||
chrome.windows.create(options, (win) => {
|
chrome.windows.create(options, (win) => {
|
||||||
windows[position] = win.id;
|
windows[position] = win.id;
|
||||||
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
if (navigator.userAgent.indexOf('Firefox') !== -1) {
|
||||||
|
@ -34,11 +37,17 @@ export default function openDevToolsWindow(position) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let params = { left: 0, top: 0, width: 380, height: window.screen.availHeight };
|
let params = {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: 380,
|
||||||
|
height: window.screen.availHeight,
|
||||||
|
};
|
||||||
let url = 'window.html';
|
let url = 'window.html';
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case 'devtools-right':
|
case 'devtools-right':
|
||||||
params.left = window.screen.availLeft + window.screen.availWidth - params.width;
|
params.left =
|
||||||
|
window.screen.availLeft + window.screen.availWidth - params.width;
|
||||||
break;
|
break;
|
||||||
case 'devtools-bottom':
|
case 'devtools-bottom':
|
||||||
params.height = 420;
|
params.height = 420;
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
// Mock not supported chrome.* API for Firefox and Electron
|
// Mock not supported chrome.* API for Firefox and Electron
|
||||||
|
|
||||||
window.isElectron = window.navigator &&
|
window.isElectron =
|
||||||
window.navigator.userAgent.indexOf('Electron') !== -1;
|
window.navigator && window.navigator.userAgent.indexOf('Electron') !== -1;
|
||||||
|
|
||||||
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
const isFirefox = navigator.userAgent.indexOf('Firefox') !== -1;
|
||||||
|
|
||||||
// Background page only
|
// Background page only
|
||||||
if (
|
if (
|
||||||
window.isElectron &&
|
(window.isElectron &&
|
||||||
location.pathname === '/_generated_background_page.html' ||
|
location.pathname === '/_generated_background_page.html') ||
|
||||||
isFirefox
|
isFirefox
|
||||||
) {
|
) {
|
||||||
chrome.runtime.onConnectExternal = {
|
chrome.runtime.onConnectExternal = {
|
||||||
addListener() {}
|
addListener() {},
|
||||||
};
|
};
|
||||||
chrome.runtime.onMessageExternal = {
|
chrome.runtime.onMessageExternal = {
|
||||||
addListener() {}
|
addListener() {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (window.isElectron) {
|
if (window.isElectron) {
|
||||||
chrome.notifications = {
|
chrome.notifications = {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
addListener() {}
|
addListener() {},
|
||||||
},
|
},
|
||||||
create() {},
|
create() {},
|
||||||
clear() {}
|
clear() {},
|
||||||
};
|
};
|
||||||
chrome.contextMenus = {
|
chrome.contextMenus = {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
addListener() {}
|
addListener() {},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
chrome.storage.sync = chrome.storage.local;
|
chrome.storage.sync = chrome.storage.local;
|
||||||
chrome.runtime.onInstalled = {
|
chrome.runtime.onInstalled = {
|
||||||
addListener: cb => cb()
|
addListener: (cb) => cb(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ if (window.isElectron) {
|
||||||
if (!chrome.storage.local || !chrome.storage.local.remove) {
|
if (!chrome.storage.local || !chrome.storage.local.remove) {
|
||||||
chrome.storage.local = {
|
chrome.storage.local = {
|
||||||
set(obj, callback) {
|
set(obj, callback) {
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach((key) => {
|
||||||
localStorage.setItem(key, obj[key]);
|
localStorage.setItem(key, obj[key]);
|
||||||
});
|
});
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
@ -52,7 +52,7 @@ if (window.isElectron) {
|
||||||
},
|
},
|
||||||
get(obj, callback) {
|
get(obj, callback) {
|
||||||
const result = {};
|
const result = {};
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach((key) => {
|
||||||
result[key] = localStorage.getItem(key) || obj[key];
|
result[key] = localStorage.getItem(key) || obj[key];
|
||||||
});
|
});
|
||||||
if (callback) {
|
if (callback) {
|
||||||
|
@ -62,7 +62,7 @@ if (window.isElectron) {
|
||||||
// Electron ~ 1.4.6
|
// Electron ~ 1.4.6
|
||||||
remove(items, callback) {
|
remove(items, callback) {
|
||||||
if (Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
items.forEach(name => {
|
items.forEach((name) => {
|
||||||
localStorage.removeItem(name);
|
localStorage.removeItem(name);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -71,7 +71,7 @@ if (window.isElectron) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Avoid error: chrome.runtime.sendMessage is not supported responseCallback
|
// Avoid error: chrome.runtime.sendMessage is not supported responseCallback
|
||||||
|
|
|
@ -18,7 +18,9 @@ let preloadedState;
|
||||||
|
|
||||||
const isChrome = navigator.userAgent.indexOf('Firefox') === -1;
|
const isChrome = navigator.userAgent.indexOf('Firefox') === -1;
|
||||||
|
|
||||||
getPreloadedState(position, state => { preloadedState = state; });
|
getPreloadedState(position, (state) => {
|
||||||
|
preloadedState = state;
|
||||||
|
});
|
||||||
|
|
||||||
function renderDevTools() {
|
function renderDevTools() {
|
||||||
const node = document.getElementById('root');
|
const node = document.getElementById('root');
|
||||||
|
@ -40,15 +42,29 @@ function renderNA() {
|
||||||
naTimeout = setTimeout(() => {
|
naTimeout = setTimeout(() => {
|
||||||
let message = (
|
let message = (
|
||||||
<div style={messageStyle}>
|
<div style={messageStyle}>
|
||||||
No store found. Make sure to follow <a href="https://github.com/zalmoxisus/redux-devtools-extension#usage" target="_blank">the instructions</a>.
|
No store found. Make sure to follow{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/zalmoxisus/redux-devtools-extension#usage"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
the instructions
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
if (isChrome) {
|
if (isChrome) {
|
||||||
chrome.devtools.inspectedWindow.getResources(resources => {
|
chrome.devtools.inspectedWindow.getResources((resources) => {
|
||||||
if (resources[0].url.substr(0, 4) === 'file') {
|
if (resources[0].url.substr(0, 4) === 'file') {
|
||||||
message = (
|
message = (
|
||||||
<div style={messageStyle}>
|
<div style={messageStyle}>
|
||||||
No store found. Most likely you did not allow access to file URLs. <a href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#access-file-url-file" target="_blank">See details</a>.
|
No store found. Most likely you did not allow access to file URLs.{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/Troubleshooting.md#access-file-url-file"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
See details
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -69,8 +85,10 @@ function renderNA() {
|
||||||
|
|
||||||
function init(id) {
|
function init(id) {
|
||||||
renderNA();
|
renderNA();
|
||||||
bgConnection = chrome.runtime.connect({ name: id ? id.toString() : undefined });
|
bgConnection = chrome.runtime.connect({
|
||||||
bgConnection.onMessage.addListener(message => {
|
name: id ? id.toString() : undefined,
|
||||||
|
});
|
||||||
|
bgConnection.onMessage.addListener((message) => {
|
||||||
if (message.type === 'NA') {
|
if (message.type === 'NA') {
|
||||||
if (message.id === id) renderNA();
|
if (message.id === id) renderNA();
|
||||||
else store.dispatch({ type: REMOVE_INSTANCE, id: message.id });
|
else store.dispatch({ type: REMOVE_INSTANCE, id: message.id });
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
function createPanel(url) {
|
function createPanel(url) {
|
||||||
chrome.devtools.panels.create(
|
chrome.devtools.panels.create(
|
||||||
'Redux', 'img/logo/scalable.png', url, function() {}
|
'Redux',
|
||||||
|
'img/logo/scalable.png',
|
||||||
|
url,
|
||||||
|
function () {}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chrome.runtime.getBackgroundPage) {
|
if (chrome.runtime.getBackgroundPage) {
|
||||||
// Check if the background page's object is accessible (not in incognito)
|
// Check if the background page's object is accessible (not in incognito)
|
||||||
chrome.runtime.getBackgroundPage(background => {
|
chrome.runtime.getBackgroundPage((background) => {
|
||||||
createPanel(background ? 'window.html' : 'devpanel.html');
|
createPanel(background ? 'window.html' : 'devpanel.html');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user