mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2026-03-10 14:42:41 +03:00
Compare commits
No commits in common. "main" and "remotedev-redux-devtools-extension@3.1.2" have entirely different histories.
main
...
remotedev-
|
|
@ -6,8 +6,5 @@
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": [],
|
"ignore": []
|
||||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
|
||||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
.gitattributes
vendored
15
.gitattributes
vendored
|
|
@ -1 +1,14 @@
|
||||||
* text=auto eol=lf
|
*.js text eol=lf
|
||||||
|
*.jsx text eol=lf
|
||||||
|
*.ts text eol=lf
|
||||||
|
*.tsx text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.html text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.graphql text eol=lf
|
||||||
|
.eslintrc text eol=lf
|
||||||
|
.prettierrc text eol=lf
|
||||||
|
.babelrc text eol=lf
|
||||||
|
.stylelintrc text eol=lf
|
||||||
|
|
|
||||||
13
.github/workflows/CI.yml
vendored
13
.github/workflows/CI.yml
vendored
|
|
@ -8,12 +8,15 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: 'ubuntu-22.04'
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
with:
|
||||||
- uses: actions/setup-node@v6
|
fetch-depth: 0
|
||||||
|
- uses: nrwl/nx-set-shas@v3
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
@ -26,6 +29,6 @@ jobs:
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm run lint:all
|
run: pnpm run lint:all
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: coactions/setup-xvfb@v1
|
uses: GabrielBB/xvfb-action@v1
|
||||||
with:
|
with:
|
||||||
run: pnpm run test:all
|
run: pnpm run test:all
|
||||||
|
|
|
||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -10,18 +10,18 @@ permissions: write-all
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: 'ubuntu-22.04'
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 'lts/*'
|
node-version: 'lts/*'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
@ -40,19 +40,19 @@ jobs:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
- name: Archive Chrome Extension
|
- name: Archive Chrome Extension
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: chrome
|
name: chrome
|
||||||
path: extension/chrome/dist
|
path: extension/chrome/dist
|
||||||
|
|
||||||
- name: Archive Edge Extension
|
- name: Archive Edge Extension
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: edge
|
name: edge
|
||||||
path: extension/edge/dist
|
path: extension/edge/dist
|
||||||
|
|
||||||
- name: Archive Firefox Extension
|
- name: Archive Firefox Extension
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: firefox
|
name: firefox
|
||||||
path: extension/firefox/dist
|
path: extension/firefox/dist
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,4 +9,3 @@ coverage
|
||||||
.idea
|
.idea
|
||||||
.eslintcache
|
.eslintcache
|
||||||
!packages/redux-devtools-slider-monitor/examples/todomvc/dist/index.html
|
!packages/redux-devtools-slider-monitor/examples/todomvc/dist/index.html
|
||||||
.nx
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ It can be used as a browser extension (for [Chrome](https://chrome.google.com/we
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
This is a monorepo powered by [pnpm](https://pnpm.io/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use pnpm workspace commands to build only the packages necessary for the packages you're working on (i.e., `pnpm --filter "remotedev-redux-devtools-extension" build`).
|
This is a monorepo powered by [pnpm](https://pnpm.io/) and [Nx](https://nx.dev/). [Install pnpm](https://pnpm.io/installation) and run `pnpm install` to get started. Each package's dependencies need to be built before the package itself can be built. You can either build all the packages (i.e., `pnpm run build:all`) or use Nx commands to build only the packages necessary for the packages you're working on (i.e., `pnpm nx build remotedev-redux-devtools-extension`).
|
||||||
|
|
||||||
## Backers
|
## Backers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default defineConfig([eslint.configs.recommended, eslintConfigPrettier]);
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import react from 'eslint-plugin-react';
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
|
||||||
import jest from 'eslint-plugin-jest';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default defineConfig([
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...eslint.configs.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...react.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...reactHooks.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...jest.configs['flat/recommended'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...jest.configs['jest/style'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
...eslintConfigPrettier,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default (tsconfigRootDir, files = ['**/*.ts'], project = true) =>
|
|
||||||
defineConfig([
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...eslint.configs.recommended,
|
|
||||||
},
|
|
||||||
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
|
||||||
files,
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
|
||||||
files,
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project,
|
|
||||||
tsconfigRootDir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...eslintConfigPrettier,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
|
||||||
'@typescript-eslint/no-base-to-string': 'off',
|
|
||||||
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
||||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/prefer-for-of': 'off',
|
|
||||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
|
||||||
'@typescript-eslint/class-literal-property-style': 'off',
|
|
||||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
|
||||||
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/array-type': 'off',
|
|
||||||
'@typescript-eslint/prefer-function-type': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import jest from 'eslint-plugin-jest';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default (tsconfigRootDir) =>
|
|
||||||
defineConfig([
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...eslint.configs.recommended,
|
|
||||||
},
|
|
||||||
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.test.json'],
|
|
||||||
tsconfigRootDir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...jest.configs['flat/recommended'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...jest.configs['jest/style'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
...eslintConfigPrettier,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
|
||||||
'@typescript-eslint/no-base-to-string': 'off',
|
|
||||||
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
||||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/prefer-for-of': 'off',
|
|
||||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
|
||||||
'@typescript-eslint/class-literal-property-style': 'off',
|
|
||||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
|
||||||
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/array-type': 'off',
|
|
||||||
'@typescript-eslint/prefer-function-type': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import react from 'eslint-plugin-react';
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default (
|
|
||||||
tsconfigRootDir,
|
|
||||||
files = ['**/*.ts', '**/*.tsx'],
|
|
||||||
project = true,
|
|
||||||
) =>
|
|
||||||
defineConfig([
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...eslint.configs.recommended,
|
|
||||||
},
|
|
||||||
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
|
||||||
files,
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
|
||||||
files,
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project,
|
|
||||||
tsconfigRootDir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...react.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...reactHooks.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
...eslintConfigPrettier,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files,
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
||||||
'@typescript-eslint/no-misused-promises': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
checksVoidReturn: {
|
|
||||||
attributes: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
|
||||||
'@typescript-eslint/no-base-to-string': 'off',
|
|
||||||
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
||||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/prefer-for-of': 'off',
|
|
||||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
|
||||||
'@typescript-eslint/class-literal-property-style': 'off',
|
|
||||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
|
||||||
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/array-type': 'off',
|
|
||||||
'@typescript-eslint/prefer-function-type': 'off',
|
|
||||||
'react/prop-types': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
import { defineConfig } from 'eslint/config';
|
|
||||||
import eslint from '@eslint/js';
|
|
||||||
import tseslint from 'typescript-eslint';
|
|
||||||
import react from 'eslint-plugin-react';
|
|
||||||
import reactHooks from 'eslint-plugin-react-hooks';
|
|
||||||
import jest from 'eslint-plugin-jest';
|
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier';
|
|
||||||
|
|
||||||
export default (tsconfigRootDir) =>
|
|
||||||
defineConfig([
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...eslint.configs.recommended,
|
|
||||||
},
|
|
||||||
...tseslint.configs.recommendedTypeChecked.map((config) => ({
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
...tseslint.configs.stylisticTypeChecked.map((config) => ({
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...config,
|
|
||||||
})),
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.test.json'],
|
|
||||||
tsconfigRootDir,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...react.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: 'detect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...reactHooks.configs.flat.recommended,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...jest.configs['flat/recommended'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...jest.configs['jest/style'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
...eslintConfigPrettier,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.ts', 'test/**/*.tsx'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
|
||||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
|
||||||
'@typescript-eslint/no-base-to-string': 'off',
|
|
||||||
'@typescript-eslint/consistent-indexed-object-style': 'off',
|
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
|
||||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
||||||
'@typescript-eslint/no-unused-vars': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
'@typescript-eslint/prefer-for-of': 'off',
|
|
||||||
'@typescript-eslint/non-nullable-type-assertion-style': 'off',
|
|
||||||
'@typescript-eslint/class-literal-property-style': 'off',
|
|
||||||
'@typescript-eslint/no-redundant-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
|
||||||
'@typescript-eslint/no-duplicate-type-constituents': 'off',
|
|
||||||
'@typescript-eslint/array-type': 'off',
|
|
||||||
'@typescript-eslint/prefer-function-type': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
3
eslintrc.js.base.json
Normal file
3
eslintrc.js.base.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"parser": "@babel/eslint-parser"
|
||||||
|
}
|
||||||
31
eslintrc.ts.base.json
Normal file
31
eslintrc.ts.base.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
|
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/prefer-optional-chain": "off",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": "off",
|
||||||
|
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/prefer-for-of": "off",
|
||||||
|
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||||
|
"@typescript-eslint/class-literal-property-style": "off",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "off",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "off",
|
||||||
|
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
||||||
|
"@typescript-eslint/array-type": "off",
|
||||||
|
"@typescript-eslint/prefer-function-type": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
eslintrc.ts.jest.base.json
Normal file
32
eslintrc.ts.jest.base.json
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"plugins": ["jest"],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
|
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||||
|
"plugin:jest/recommended",
|
||||||
|
"plugin:jest/style",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/prefer-optional-chain": "off",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": "off",
|
||||||
|
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/prefer-for-of": "off",
|
||||||
|
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||||
|
"@typescript-eslint/class-literal-property-style": "off",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "off",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "off",
|
||||||
|
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
||||||
|
"@typescript-eslint/array-type": "off",
|
||||||
|
"@typescript-eslint/prefer-function-type": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
eslintrc.ts.react.base.json
Normal file
51
eslintrc.ts.react.base.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint", "react"],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
|
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/no-misused-promises": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"checksVoidReturn": {
|
||||||
|
"attributes": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@typescript-eslint/prefer-optional-chain": "off",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": "off",
|
||||||
|
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/prefer-for-of": "off",
|
||||||
|
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||||
|
"@typescript-eslint/class-literal-property-style": "off",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "off",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "off",
|
||||||
|
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
||||||
|
"@typescript-eslint/array-type": "off",
|
||||||
|
"@typescript-eslint/prefer-function-type": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
eslintrc.ts.react.jest.base.json
Normal file
34
eslintrc.ts.react.jest.base.json
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"plugins": ["jest"],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
|
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"plugin:jest/recommended",
|
||||||
|
"plugin:jest/style",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-call": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/prefer-optional-chain": "off",
|
||||||
|
"@typescript-eslint/no-base-to-string": "off",
|
||||||
|
"@typescript-eslint/consistent-indexed-object-style": "off",
|
||||||
|
"@typescript-eslint/prefer-nullish-coalescing": "off",
|
||||||
|
"@typescript-eslint/consistent-type-definitions": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/prefer-for-of": "off",
|
||||||
|
"@typescript-eslint/non-nullable-type-assertion-style": "off",
|
||||||
|
"@typescript-eslint/class-literal-property-style": "off",
|
||||||
|
"@typescript-eslint/no-redundant-type-constituents": "off",
|
||||||
|
"@typescript-eslint/prefer-string-starts-ends-with": "off",
|
||||||
|
"@typescript-eslint/no-duplicate-type-constituents": "off",
|
||||||
|
"@typescript-eslint/array-type": "off",
|
||||||
|
"@typescript-eslint/prefer-function-type": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
extension/.eslintignore
Normal file
3
extension/.eslintignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
examples
|
||||||
31
extension/.eslintrc
Normal file
31
extension/.eslintrc
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": "eslint-config-airbnb",
|
||||||
|
"globals": {
|
||||||
|
"chrome": true,
|
||||||
|
"__DEVELOPMENT__": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-uses-react": 2,
|
||||||
|
"react/jsx-uses-vars": 2,
|
||||||
|
"react/react-in-jsx-scope": 2,
|
||||||
|
"react/jsx-quotes": 0,
|
||||||
|
"block-scoped-var": 0,
|
||||||
|
"padded-blocks": 0,
|
||||||
|
"quotes": [1, "single"],
|
||||||
|
"comma-style": [2, "last"],
|
||||||
|
"no-use-before-define": [0, "nofunc"],
|
||||||
|
"func-names": 0,
|
||||||
|
"prefer-const": 0,
|
||||||
|
"comma-dangle": 0,
|
||||||
|
"id-length": 0,
|
||||||
|
"indent": [2, 2, { "SwitchCase": 1 }],
|
||||||
|
"new-cap": [2, { "capIsNewExceptions": ["Test"] }],
|
||||||
|
"default-case": 0
|
||||||
|
},
|
||||||
|
"plugins": ["react"]
|
||||||
|
}
|
||||||
|
|
@ -1,172 +1,5 @@
|
||||||
# remotedev-redux-devtools-extension
|
# remotedev-redux-devtools-extension
|
||||||
|
|
||||||
## 3.2.12
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [3f90241]
|
|
||||||
- Updated dependencies [d61d31a]
|
|
||||||
- Updated dependencies [804e729]
|
|
||||||
- Updated dependencies [12849a4]
|
|
||||||
- Updated dependencies [804d6bd]
|
|
||||||
- Updated dependencies [6481386]
|
|
||||||
- @redux-devtools/instrument@3.0.0
|
|
||||||
- @redux-devtools/ui@3.0.0
|
|
||||||
- @redux-devtools/slider-monitor@7.0.0
|
|
||||||
- @redux-devtools/core@5.0.0
|
|
||||||
- @redux-devtools/app@8.0.0
|
|
||||||
- @redux-devtools/serialize@1.0.0
|
|
||||||
- @redux-devtools/utils@4.0.0
|
|
||||||
|
|
||||||
## 3.2.11
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [6163276]
|
|
||||||
- @redux-devtools/app@7.0.0
|
|
||||||
- @redux-devtools/slider-monitor@6.0.0
|
|
||||||
- @redux-devtools/ui@2.0.0
|
|
||||||
|
|
||||||
## 3.2.10
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- @redux-devtools/app@6.2.2
|
|
||||||
|
|
||||||
## 3.2.9
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [91f21b2]
|
|
||||||
- @redux-devtools/core@4.1.1
|
|
||||||
- @redux-devtools/slider-monitor@5.1.1
|
|
||||||
- @redux-devtools/utils@3.1.1
|
|
||||||
- @redux-devtools/app@6.2.1
|
|
||||||
|
|
||||||
## 3.2.8
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [6830118]
|
|
||||||
- react-json-tree@0.20.0
|
|
||||||
- @redux-devtools/app@6.2.0
|
|
||||||
- @redux-devtools/slider-monitor@6.0.0
|
|
||||||
- @redux-devtools/ui@1.4.0
|
|
||||||
- @redux-devtools/core@4.1.0
|
|
||||||
- @redux-devtools/utils@4.0.0
|
|
||||||
|
|
||||||
## 3.2.7
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- b25bf13: Send state from background when monitor connects
|
|
||||||
|
|
||||||
## 3.2.6
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- 50d7682: Fix DevTools from losing connection
|
|
||||||
|
|
||||||
## 3.2.5
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- eb3ac09: Add logging to background service worker
|
|
||||||
|
|
||||||
## 3.2.4
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- f1d6158: Fix mocking Chrome API for Electron
|
|
||||||
|
|
||||||
## 3.2.3
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- fd9f950: Fix monitoring on opening panel
|
|
||||||
- e49708d: Fix manifest.json for Edge
|
|
||||||
|
|
||||||
## 3.2.1
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- abd03a7: Fix: only send data to extension if DevTools are open
|
|
||||||
|
|
||||||
## 3.2.0
|
|
||||||
|
|
||||||
### Minor Changes
|
|
||||||
|
|
||||||
- 83b2c19: Upgrade to Manifest V3
|
|
||||||
|
|
||||||
## 3.1.11
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- 73688e1: Fix releasing Firefox extension
|
|
||||||
|
|
||||||
## 3.1.10
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- 2163bc3: Split large messages sent from background page to devpanel
|
|
||||||
|
|
||||||
## 3.1.9
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [bbb1a40]
|
|
||||||
- react-json-tree@0.19.0
|
|
||||||
- @redux-devtools/slider-monitor@5.0.1
|
|
||||||
- @redux-devtools/ui@1.3.2
|
|
||||||
|
|
||||||
## 3.1.8
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- 191d419: Convert d3 packages to ESM
|
|
||||||
- Updated dependencies [191d419]
|
|
||||||
- @redux-devtools/app@6.0.1
|
|
||||||
|
|
||||||
## 3.1.7
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [5cfe3e5]
|
|
||||||
- Updated dependencies [decc035]
|
|
||||||
- @redux-devtools/app@6.0.0
|
|
||||||
- @redux-devtools/slider-monitor@5.0.0
|
|
||||||
- @redux-devtools/core@4.0.0
|
|
||||||
- @redux-devtools/utils@3.0.0
|
|
||||||
|
|
||||||
## 3.1.6
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [158ba2c]
|
|
||||||
- @redux-devtools/app@5.0.0
|
|
||||||
|
|
||||||
## 3.1.5
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- 65205f90: Replace Action<unknown> with Action<string>
|
|
||||||
- Updated dependencies [65205f90]
|
|
||||||
- @redux-devtools/app@4.0.1
|
|
||||||
- @redux-devtools/core@3.13.2
|
|
||||||
|
|
||||||
## 3.1.4
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- Updated dependencies [e57bcb39]
|
|
||||||
- @redux-devtools/app@4.0.0
|
|
||||||
|
|
||||||
## 3.1.3
|
|
||||||
|
|
||||||
### Patch Changes
|
|
||||||
|
|
||||||
- bca76009: Fix missing CSS for code editor
|
|
||||||
|
|
||||||
## 3.1.2
|
## 3.1.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
|
|
||||||
- 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/docs/extensions/get-started/tutorial/hello-world#load-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/docs/extensions/get-started/tutorial/hello-world#load-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
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ const composeEnhancers =
|
||||||
compose;
|
compose;
|
||||||
```
|
```
|
||||||
|
|
||||||
> For TypeScript use [`redux-devtools-extension` npm package](#13-use-redux-devtoolsextension-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-devtoolsextension-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;
|
||||||
|
|
@ -228,7 +228,7 @@ See [integrations](docs/Integrations.md) and [the blog post](https://medium.com/
|
||||||
- [Methods (advanced API)](docs/API/Methods.md)
|
- [Methods (advanced API)](docs/API/Methods.md)
|
||||||
- [FAQ](docs/FAQ.md)
|
- [FAQ](docs/FAQ.md)
|
||||||
- Features
|
- Features
|
||||||
- [Trace actions calls](docs/Features/Trace.md)
|
- [Trace actions calls](/docs/Features/Trace.md)
|
||||||
- [Troubleshooting](docs/Troubleshooting.md)
|
- [Troubleshooting](docs/Troubleshooting.md)
|
||||||
- [Articles](docs/Articles.md)
|
- [Articles](docs/Articles.md)
|
||||||
- [Videos](docs/Videos.md)
|
- [Videos](docs/Videos.md)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import pug from 'pug';
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const prod = !args.includes('--dev');
|
const prod = !args.includes('--dev');
|
||||||
|
|
||||||
await esbuild.build({
|
const commonEsbuildOptions = {
|
||||||
bundle: true,
|
bundle: true,
|
||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
outdir: 'dist',
|
outdir: 'dist',
|
||||||
|
|
@ -15,24 +15,40 @@ await esbuild.build({
|
||||||
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
|
'process.env.NODE_ENV': prod ? '"production"' : '"development"',
|
||||||
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
|
'process.env.BABEL_ENV': prod ? '"production"' : '"development"',
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await esbuild.build({
|
||||||
|
...commonEsbuildOptions,
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
{ out: 'background.bundle', in: 'src/background/index.ts' },
|
{ out: 'background.bundle', in: 'src/background/index.ts' },
|
||||||
{ out: 'options.bundle', in: 'src/options/index.tsx' },
|
{ out: 'options.bundle', in: 'src/options/index.tsx' },
|
||||||
|
{ out: 'window.bundle', in: 'src/window/index.tsx' },
|
||||||
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
|
{ out: 'remote.bundle', in: 'src/remote/index.tsx' },
|
||||||
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
|
{ out: 'devpanel.bundle', in: 'src/devpanel/index.tsx' },
|
||||||
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
|
{ out: 'devtools.bundle', in: 'src/devtools/index.ts' },
|
||||||
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
|
{ out: 'content.bundle', in: 'src/contentScript/index.ts' },
|
||||||
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
|
{ out: 'page.bundle', in: 'src/pageScript/index.ts' },
|
||||||
|
...(prod ? [] : [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }]),
|
||||||
],
|
],
|
||||||
loader: {
|
loader: {
|
||||||
'.woff2': 'file',
|
'.woff2': 'file',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (prod) {
|
||||||
|
await esbuild.build({
|
||||||
|
...commonEsbuildOptions,
|
||||||
|
entryPoints: [{ out: 'pagewrap.bundle', in: 'src/pageScriptWrap.ts' }],
|
||||||
|
loader: {
|
||||||
|
'.js': 'text',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
console.log('Creating HTML files...');
|
console.log('Creating HTML files...');
|
||||||
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote'];
|
const htmlFiles = ['devpanel', 'devtools', 'options', 'remote', 'window'];
|
||||||
for (const htmlFile of htmlFiles) {
|
for (const htmlFile of htmlFiles) {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
`dist/${htmlFile}.html`,
|
`dist/${htmlFile}.html`,
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,28 @@
|
||||||
{
|
{
|
||||||
"version": "3.2.10",
|
"version": "3.1.2",
|
||||||
"name": "Redux DevTools",
|
"name": "Redux DevTools",
|
||||||
"description": "Redux DevTools for debugging application's state changes.",
|
"description": "Redux DevTools for debugging application's state changes.",
|
||||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||||
"manifest_version": 3,
|
"manifest_version": 2,
|
||||||
"action": {
|
"page_action": {
|
||||||
"default_icon": "img/logo/gray.png",
|
"default_icon": "img/logo/gray.png",
|
||||||
"default_title": "Redux DevTools",
|
"default_title": "Redux DevTools",
|
||||||
"default_popup": "devpanel.html#popup"
|
"default_popup": "window.html#popup"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"devtools-window": {
|
"devtools-left": {
|
||||||
"description": "DevTools window"
|
"description": "DevTools window to left"
|
||||||
|
},
|
||||||
|
"devtools-right": {
|
||||||
|
"description": "DevTools window to right"
|
||||||
|
},
|
||||||
|
"devtools-bottom": {
|
||||||
|
"description": "DevTools window to bottom"
|
||||||
},
|
},
|
||||||
"devtools-remote": {
|
"devtools-remote": {
|
||||||
"description": "Remote DevTools"
|
"description": "Remote DevTools"
|
||||||
},
|
},
|
||||||
"_execute_action": {
|
"_execute_page_action": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+E"
|
"default": "Ctrl+Shift+E"
|
||||||
}
|
}
|
||||||
|
|
@ -28,37 +34,36 @@
|
||||||
"128": "img/logo/128x128.png"
|
"128": "img/logo/128x128.png"
|
||||||
},
|
},
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html"
|
"page": "options.html",
|
||||||
|
"chrome_style": true
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.bundle.js"
|
"scripts": ["background.bundle.js"],
|
||||||
|
"persistent": false
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"exclude_globs": ["https://www.google*"],
|
"exclude_globs": ["https://www.google*"],
|
||||||
"js": ["content.bundle.js"],
|
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"matches": ["<all_urls>"],
|
|
||||||
"exclude_globs": ["https://www.google*"],
|
|
||||||
"js": ["page.bundle.js"],
|
|
||||||
"run_at": "document_start",
|
|
||||||
"all_frames": true,
|
|
||||||
"world": "MAIN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"devtools_page": "devtools.html",
|
"devtools_page": "devtools.html",
|
||||||
|
"web_accessible_resources": ["page.bundle.js"],
|
||||||
"externally_connectable": {
|
"externally_connectable": {
|
||||||
"ids": ["*"]
|
"ids": ["*"]
|
||||||
},
|
},
|
||||||
"permissions": ["notifications", "contextMenus", "storage"],
|
"permissions": [
|
||||||
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
"notifications",
|
||||||
"content_security_policy": {
|
"contextMenus",
|
||||||
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
"storage",
|
||||||
},
|
"file:///*",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;",
|
||||||
"update_url": "https://clients2.google.com/service/update2/crx",
|
"update_url": "https://clients2.google.com/service/update2/crx",
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdJEPwY92xUACA9CcDBDBmbdbp8Ap3cKQ0DJTUuVQvqb4FQAv8RtKY3iUjGvdwuAcSJQIZwHXcP2aNDH3TiFik/NhRK2GRW8X3OZyTdkuDueABGP2KEX8q1WQDgjX/rPIinGYztUrvoICw/UerMPwNW62jwGoVU3YhAGf+15CgX2Y6a4tppnf/+1mPedKPidh0RsM+aJY98rX+r1SPAHPcGzMjocLkqcT75DZBXer8VQN14tOOzRCd6T6oy7qm7eWru8lJwcY66qMQvhk0osqEod2G3nA7aTWpmqPFS66VEiecP9PgZlp8gQdgZ3dFhA62exydlD55JuRhiMIR63yQIDAQAB"
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdJEPwY92xUACA9CcDBDBmbdbp8Ap3cKQ0DJTUuVQvqb4FQAv8RtKY3iUjGvdwuAcSJQIZwHXcP2aNDH3TiFik/NhRK2GRW8X3OZyTdkuDueABGP2KEX8q1WQDgjX/rPIinGYztUrvoICw/UerMPwNW62jwGoVU3YhAGf+15CgX2Y6a4tppnf/+1mPedKPidh0RsM+aJY98rX+r1SPAHPcGzMjocLkqcT75DZBXer8VQN14tOOzRCd6T6oy7qm7eWru8lJwcY66qMQvhk0osqEod2G3nA7aTWpmqPFS66VEiecP9PgZlp8gQdgZ3dFhA62exydlD55JuRhiMIR63yQIDAQAB"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ _number_ - maximum stack trace frames to be stored (in case `trace` option was p
|
||||||
_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.
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
# Architecture Notes
|
|
||||||
|
|
||||||
This document exists to keep track of how the different parts of the Redux DevTools interact, since it's easy to forget how it all works together. This is intended for internal purposes and is just a collection of notes to myself.
|
|
||||||
|
|
||||||
## Entry Points
|
|
||||||
|
|
||||||
### Window
|
|
||||||
|
|
||||||
This is the default view that is shown in the Redux DevTools popup, the Chrome DevTools tab (if direct access to the background page is available), and new popup windows that are created. It has direct access to the background page via `chrome.runtime.getBackgroundPage`.
|
|
||||||
|
|
||||||
### DevPanel
|
|
||||||
|
|
||||||
This is the view that is shown in the Chrome DevTools tab if direct access to the background page is not available.
|
|
||||||
|
|
||||||
Initially this was the view that was always used for the Chrome DevTools tab, but when support to directly access the background page from the DevTools tab was added, [the Window View became the preferred view](https://github.com/zalmoxisus/redux-devtools-extension/pull/580).
|
|
||||||
|
|
||||||
### Remote
|
|
||||||
|
|
||||||
This does not interact with the other parts of the extension at all, it just renders the `App` component from `@redux-devtools/app`.
|
|
||||||
|
|
||||||
It can be triggered by hitting the "Remote" button in any of the other views, which calls `chrome.windows.create` and creates a new window.
|
|
||||||
|
|
||||||
### DevTools
|
|
||||||
|
|
||||||
This is the script that adds the Redux panel in the Chrome DevTools using `chrome.devtools.panels.create`.
|
|
||||||
|
|
||||||
It creates a Window View if it has direct access to the background page, otherwise it creates a DevPanel View.
|
|
||||||
|
|
||||||
Note that this used to always show the DevPanel View, but [started using the Window View by default](https://github.com/zalmoxisus/redux-devtools-extension/pull/580) once direct access to the background page was added to Chrome DevTools tabs.
|
|
||||||
|
|
||||||
### Content Script
|
|
||||||
|
|
||||||
Passes messages between the injected page script and the background page.
|
|
||||||
|
|
||||||
It listens for messages from the injected page script using `window.addEventListener('message', ...)`. It knows the message is from the injected page script if `message.source` is `'@devtools-page'`. See the Chrome DevTools docs where this approach [is documented](https://developer.chrome.com/docs/extensions/how-to/devtools/extend-devtools#evaluated-scripts-to-devtools).
|
|
||||||
|
|
||||||
It creates a connection to the background page using `chrome.runtime.connect` with the name `'tab'` when it receives the first message from the injected page script.
|
|
||||||
|
|
@ -1,22 +1,28 @@
|
||||||
{
|
{
|
||||||
"version": "3.2.10",
|
"version": "3.1.2",
|
||||||
"name": "Redux DevTools",
|
"name": "Redux DevTools",
|
||||||
"description": "Redux DevTools for debugging application's state changes.",
|
"description": "Redux DevTools for debugging application's state changes.",
|
||||||
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
"homepage_url": "https://github.com/reduxjs/redux-devtools",
|
||||||
"manifest_version": 3,
|
"manifest_version": 2,
|
||||||
"action": {
|
"page_action": {
|
||||||
"default_icon": "img/logo/gray.png",
|
"default_icon": "img/logo/gray.png",
|
||||||
"default_title": "Redux DevTools",
|
"default_title": "Redux DevTools",
|
||||||
"default_popup": "devpanel.html#popup"
|
"default_popup": "window.html#popup"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"devtools-window": {
|
"devtools-left": {
|
||||||
"description": "DevTools window"
|
"description": "DevTools window to left"
|
||||||
|
},
|
||||||
|
"devtools-right": {
|
||||||
|
"description": "DevTools window to right"
|
||||||
|
},
|
||||||
|
"devtools-bottom": {
|
||||||
|
"description": "DevTools window to bottom"
|
||||||
},
|
},
|
||||||
"devtools-remote": {
|
"devtools-remote": {
|
||||||
"description": "Remote DevTools"
|
"description": "Remote DevTools"
|
||||||
},
|
},
|
||||||
"_execute_action": {
|
"_execute_page_action": {
|
||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+E"
|
"default": "Ctrl+Shift+E"
|
||||||
}
|
}
|
||||||
|
|
@ -28,35 +34,34 @@
|
||||||
"128": "img/logo/128x128.png"
|
"128": "img/logo/128x128.png"
|
||||||
},
|
},
|
||||||
"options_ui": {
|
"options_ui": {
|
||||||
"page": "options.html"
|
"page": "options.html",
|
||||||
|
"chrome_style": true
|
||||||
},
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.bundle.js"
|
"scripts": ["background.bundle.js"],
|
||||||
|
"persistent": false
|
||||||
},
|
},
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["<all_urls>"],
|
"matches": ["<all_urls>"],
|
||||||
"exclude_globs": ["https://www.google*"],
|
"exclude_globs": ["https://www.google*"],
|
||||||
"js": ["content.bundle.js"],
|
"js": ["content.bundle.js", "pagewrap.bundle.js"],
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"matches": ["<all_urls>"],
|
|
||||||
"exclude_globs": ["https://www.google*"],
|
|
||||||
"js": ["page.bundle.js"],
|
|
||||||
"run_at": "document_start",
|
|
||||||
"all_frames": true,
|
|
||||||
"world": "MAIN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"devtools_page": "devtools.html",
|
"devtools_page": "devtools.html",
|
||||||
|
"web_accessible_resources": ["page.bundle.js"],
|
||||||
"externally_connectable": {
|
"externally_connectable": {
|
||||||
"ids": ["*"]
|
"ids": ["*"]
|
||||||
},
|
},
|
||||||
"permissions": ["notifications", "contextMenus", "storage"],
|
"permissions": [
|
||||||
"host_permissions": ["file:///*", "http://*/*", "https://*/*"],
|
"notifications",
|
||||||
"content_security_policy": {
|
"contextMenus",
|
||||||
"extension_pages": "script-src 'self'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
"storage",
|
||||||
}
|
"file:///*",
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'; style-src * 'unsafe-inline'; img-src 'self' data:;"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import globals from 'globals';
|
|
||||||
import eslintJs from '../eslint.js.config.base.mjs';
|
|
||||||
import eslintTsReact from '../eslint.ts.react.config.base.mjs';
|
|
||||||
import eslintJsReactJest from '../eslint.js.react.jest.config.base.mjs';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
...eslintJs,
|
|
||||||
...eslintTsReact(import.meta.dirname),
|
|
||||||
...eslintJsReactJest,
|
|
||||||
{
|
|
||||||
ignores: [
|
|
||||||
'chrome',
|
|
||||||
'dist',
|
|
||||||
'edge',
|
|
||||||
'examples',
|
|
||||||
'firefox',
|
|
||||||
'jest.config.ts',
|
|
||||||
'test/electron/fixture/dist',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['build.mjs'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.nodeBuiltin,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ['test/**/*.js', 'test/**/*.jsx'],
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
EUI: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
36
extension/examples/buildAll.js
Normal file
36
extension/examples/buildAll.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* Runs an ordered set of commands within each of the build directories.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
|
||||||
|
var exampleDirs = fs.readdirSync(__dirname).filter((file) => {
|
||||||
|
return fs.statSync(path.join(__dirname, file)).isDirectory();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ordering is important here. `npm install` must come first.
|
||||||
|
var cmdArgs = [
|
||||||
|
{ cmd: 'npm', args: ['install'] },
|
||||||
|
{ cmd: 'webpack', args: ['index.js'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const dir of exampleDirs) {
|
||||||
|
for (const cmdArg of cmdArgs) {
|
||||||
|
// declare opts in this scope to avoid https://github.com/joyent/node/issues/9158
|
||||||
|
const opts = {
|
||||||
|
cwd: path.join(__dirname, dir),
|
||||||
|
stdio: 'inherit',
|
||||||
|
};
|
||||||
|
let result = {};
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
result = spawnSync(cmdArg.cmd + '.cmd', cmdArg.args, opts);
|
||||||
|
} else {
|
||||||
|
result = spawnSync(cmdArg.cmd, cmdArg.args, opts);
|
||||||
|
}
|
||||||
|
if (result.status !== 0) {
|
||||||
|
throw new Error('Building examples exited with non-zero');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
extension/examples/counter/.babelrc
Normal file
3
extension/examples/counter/.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"]
|
||||||
|
}
|
||||||
37
extension/examples/counter/actions/counter.js
Normal file
37
extension/examples/counter/actions/counter.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
|
||||||
|
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
|
||||||
|
|
||||||
|
let t;
|
||||||
|
|
||||||
|
export function increment() {
|
||||||
|
return {
|
||||||
|
type: INCREMENT_COUNTER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrement() {
|
||||||
|
return {
|
||||||
|
type: DECREMENT_COUNTER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function autoIncrement(delay = 10) {
|
||||||
|
return (dispatch) => {
|
||||||
|
if (t) {
|
||||||
|
clearInterval(t);
|
||||||
|
t = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
t = setInterval(() => {
|
||||||
|
dispatch(increment());
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function incrementAsync(delay = 1000) {
|
||||||
|
return (dispatch) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch(increment());
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
}
|
||||||
27
extension/examples/counter/components/Counter.js
Normal file
27
extension/examples/counter/components/Counter.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class Counter extends Component {
|
||||||
|
render() {
|
||||||
|
const { increment, autoIncrement, incrementAsync, decrement, counter } =
|
||||||
|
this.props;
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
Clicked: {counter} times <button onClick={increment}>+</button>{' '}
|
||||||
|
<button onClick={decrement}>-</button>{' '}
|
||||||
|
<button onClick={incrementAsync}>Increment async</button>{' '}
|
||||||
|
<button onClick={autoIncrement}>Auto increment</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter.propTypes = {
|
||||||
|
increment: PropTypes.func.isRequired,
|
||||||
|
autoIncrement: PropTypes.func.isRequired,
|
||||||
|
incrementAsync: PropTypes.func.isRequired,
|
||||||
|
decrement: PropTypes.func.isRequired,
|
||||||
|
counter: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Counter;
|
||||||
16
extension/examples/counter/containers/App.js
Normal file
16
extension/examples/counter/containers/App.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Counter from '../components/Counter';
|
||||||
|
import * as CounterActions from '../actions/counter';
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
counter: state.counter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return bindActionCreators(CounterActions, dispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
|
||||||
10
extension/examples/counter/index.html
Normal file
10
extension/examples/counter/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Redux counter example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="/static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
14
extension/examples/counter/index.js
Normal file
14
extension/examples/counter/index.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import App from './containers/App';
|
||||||
|
import configureStore from './store/configureStore';
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
41
extension/examples/counter/package.json
Normal file
41
extension/examples/counter/package.json
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"name": "redux-counter-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Redux counter example",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-core/register --require ./test/setup.js",
|
||||||
|
"test:watch": "npm test -- --watch"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rackt/redux.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/rackt/redux/issues"
|
||||||
|
},
|
||||||
|
"homepage": "http://rackt.github.io/redux",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^16.7.0",
|
||||||
|
"react-dom": "^16.7.0",
|
||||||
|
"react-redux": "^6.0.0",
|
||||||
|
"redux": "^4.0.1",
|
||||||
|
"redux-devtools-extension": "^2.13.7",
|
||||||
|
"redux-thunk": "^2.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.3.17",
|
||||||
|
"babel-core": "^6.3.17",
|
||||||
|
"babel-loader": "^7.0.0",
|
||||||
|
"babel-preset-es2015": "^6.0.0",
|
||||||
|
"babel-preset-react": "6.3.13",
|
||||||
|
"babel-preset-stage-0": "^6.3.13",
|
||||||
|
"express": "^4.13.3",
|
||||||
|
"redux-immutable-state-invariant": "^2.1.0",
|
||||||
|
"webpack": "^4.0.0",
|
||||||
|
"webpack-dev-server": "^3.0.0",
|
||||||
|
"webpack-hot-middleware": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
extension/examples/counter/reducers/counter.js
Normal file
12
extension/examples/counter/reducers/counter.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';
|
||||||
|
|
||||||
|
export default function counter(state = 0, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case INCREMENT_COUNTER:
|
||||||
|
return state + 1;
|
||||||
|
case DECREMENT_COUNTER:
|
||||||
|
return state - 1;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
extension/examples/counter/reducers/index.js
Normal file
8
extension/examples/counter/reducers/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import counter from './counter';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
counter,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
||||||
32
extension/examples/counter/server.js
Normal file
32
extension/examples/counter/server.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var webpackDevMiddleware = require('webpack-dev-middleware');
|
||||||
|
var webpackHotMiddleware = require('webpack-hot-middleware');
|
||||||
|
var config = require('./webpack.config');
|
||||||
|
|
||||||
|
var app = new require('express')();
|
||||||
|
var port = 4001;
|
||||||
|
|
||||||
|
var compiler = webpack(config);
|
||||||
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
|
app.get('/', function (req, res) {
|
||||||
|
res.sendFile(__dirname + '/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
||||||
|
port,
|
||||||
|
port,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
28
extension/examples/counter/store/configureStore.js
Normal file
28
extension/examples/counter/store/configureStore.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import invariant from 'redux-immutable-state-invariant';
|
||||||
|
import reducer from '../reducers';
|
||||||
|
import * as actionCreators from '../actions/counter';
|
||||||
|
|
||||||
|
export default function configureStore(preloadedState) {
|
||||||
|
const composeEnhancers = composeWithDevTools({
|
||||||
|
actionCreators,
|
||||||
|
trace: true,
|
||||||
|
traceLimit: 25,
|
||||||
|
});
|
||||||
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
preloadedState,
|
||||||
|
composeEnhancers(applyMiddleware(invariant(), thunk)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
// Enable Webpack hot module replacement for reducers
|
||||||
|
module.hot.accept('../reducers', () => {
|
||||||
|
store.replaceReducer(require('../reducers').default);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
73
extension/examples/counter/test/actions/counter.spec.js
Normal file
73
extension/examples/counter/test/actions/counter.spec.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import { applyMiddleware } from 'redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import * as actions from '../../actions/counter';
|
||||||
|
|
||||||
|
const middlewares = [thunk];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Creates a mock of Redux store with middleware.
|
||||||
|
*/
|
||||||
|
function mockStore(getState, expectedActions, onLastAction) {
|
||||||
|
if (!Array.isArray(expectedActions)) {
|
||||||
|
throw new Error('expectedActions should be an array of expected actions.');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof onLastAction !== 'undefined' &&
|
||||||
|
typeof onLastAction !== 'function'
|
||||||
|
) {
|
||||||
|
throw new Error('onLastAction should either be undefined or function.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockStoreWithoutMiddleware() {
|
||||||
|
return {
|
||||||
|
getState() {
|
||||||
|
return typeof getState === 'function' ? getState() : getState;
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatch(action) {
|
||||||
|
const expectedAction = expectedActions.shift();
|
||||||
|
expect(action).toEqual(expectedAction);
|
||||||
|
if (onLastAction && !expectedActions.length) {
|
||||||
|
onLastAction();
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockStoreWithMiddleware = applyMiddleware(...middlewares)(
|
||||||
|
mockStoreWithoutMiddleware,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mockStoreWithMiddleware();
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
it('increment should create increment action', () => {
|
||||||
|
expect(actions.increment()).toEqual({ type: actions.INCREMENT_COUNTER });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('decrement should create decrement action', () => {
|
||||||
|
expect(actions.decrement()).toEqual({ type: actions.DECREMENT_COUNTER });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incrementIfOdd should create increment action', (done) => {
|
||||||
|
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
||||||
|
const store = mockStore({ counter: 1 }, expectedActions, done);
|
||||||
|
store.dispatch(actions.incrementIfOdd());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incrementIfOdd shouldnt create increment action if counter is even', (done) => {
|
||||||
|
const expectedActions = [];
|
||||||
|
const store = mockStore({ counter: 2 }, expectedActions);
|
||||||
|
store.dispatch(actions.incrementIfOdd());
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incrementAsync should create increment action', (done) => {
|
||||||
|
const expectedActions = [{ type: actions.INCREMENT_COUNTER }];
|
||||||
|
const store = mockStore({ counter: 0 }, expectedActions, done);
|
||||||
|
store.dispatch(actions.incrementAsync(100));
|
||||||
|
});
|
||||||
|
});
|
||||||
53
extension/examples/counter/test/components/Counter.spec.js
Normal file
53
extension/examples/counter/test/components/Counter.spec.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import Counter from '../../components/Counter';
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const actions = {
|
||||||
|
increment: expect.createSpy(),
|
||||||
|
incrementIfOdd: expect.createSpy(),
|
||||||
|
incrementAsync: expect.createSpy(),
|
||||||
|
decrement: expect.createSpy(),
|
||||||
|
};
|
||||||
|
const component = TestUtils.renderIntoDocument(
|
||||||
|
<Counter counter={1} {...actions} />,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
component: component,
|
||||||
|
actions: actions,
|
||||||
|
buttons: TestUtils.scryRenderedDOMComponentsWithTag(component, 'button'),
|
||||||
|
p: TestUtils.findRenderedDOMComponentWithTag(component, 'p'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Counter component', () => {
|
||||||
|
it('should display count', () => {
|
||||||
|
const { p } = setup();
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: 1 times/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('first button should call increment', () => {
|
||||||
|
const { buttons, actions } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[0]);
|
||||||
|
expect(actions.increment).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('second button should call decrement', () => {
|
||||||
|
const { buttons, actions } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[1]);
|
||||||
|
expect(actions.decrement).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('third button should call incrementIfOdd', () => {
|
||||||
|
const { buttons, actions } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[2]);
|
||||||
|
expect(actions.incrementIfOdd).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fourth button should call incrementAsync', () => {
|
||||||
|
const { buttons, actions } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[3]);
|
||||||
|
expect(actions.incrementAsync).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
53
extension/examples/counter/test/containers/App.spec.js
Normal file
53
extension/examples/counter/test/containers/App.spec.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import App from '../../containers/App';
|
||||||
|
import configureStore from '../../store/configureStore';
|
||||||
|
|
||||||
|
function setup(initialState) {
|
||||||
|
const store = configureStore(initialState);
|
||||||
|
const app = TestUtils.renderIntoDocument(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
app: app,
|
||||||
|
buttons: TestUtils.scryRenderedDOMComponentsWithTag(app, 'button'),
|
||||||
|
p: TestUtils.findRenderedDOMComponentWithTag(app, 'p'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('containers', () => {
|
||||||
|
describe('App', () => {
|
||||||
|
it('should display initial count', () => {
|
||||||
|
const { p } = setup();
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: 0 times/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display updated count after increment button click', () => {
|
||||||
|
const { buttons, p } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[0]);
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: 1 times/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display updated count after decrement button click', () => {
|
||||||
|
const { buttons, p } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[1]);
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: -1 times/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldnt change if even and if odd button clicked', () => {
|
||||||
|
const { buttons, p } = setup();
|
||||||
|
TestUtils.Simulate.click(buttons[2]);
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: 0 times/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should change if odd and if odd button clicked', () => {
|
||||||
|
const { buttons, p } = setup({ counter: 1 });
|
||||||
|
TestUtils.Simulate.click(buttons[2]);
|
||||||
|
expect(p.textContent).toMatch(/^Clicked: 2 times/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
23
extension/examples/counter/test/reducers/counter.spec.js
Normal file
23
extension/examples/counter/test/reducers/counter.spec.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import counter from '../../reducers/counter';
|
||||||
|
import { INCREMENT_COUNTER, DECREMENT_COUNTER } from '../../actions/counter';
|
||||||
|
|
||||||
|
describe('reducers', () => {
|
||||||
|
describe('counter', () => {
|
||||||
|
it('should handle initial state', () => {
|
||||||
|
expect(counter(undefined, {})).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle INCREMENT_COUNTER', () => {
|
||||||
|
expect(counter(1, { type: INCREMENT_COUNTER })).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle DECREMENT_COUNTER', () => {
|
||||||
|
expect(counter(1, { type: DECREMENT_COUNTER })).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle unknown action type', () => {
|
||||||
|
expect(counter(1, { type: 'unknown' })).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
5
extension/examples/counter/test/setup.js
Normal file
5
extension/examples/counter/test/setup.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { jsdom } from 'jsdom';
|
||||||
|
|
||||||
|
global.document = jsdom('<!doctype html><html><body></body></html>');
|
||||||
|
global.window = document.defaultView;
|
||||||
|
global.navigator = global.window.navigator;
|
||||||
23
extension/examples/counter/webpack.config.js
Normal file
23
extension/examples/counter/webpack.config.js
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: ['webpack-hot-middleware/client', './index'],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/static/',
|
||||||
|
},
|
||||||
|
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
3
extension/examples/react-counter-messaging/.babelrc
Normal file
3
extension/examples/react-counter-messaging/.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"]
|
||||||
|
}
|
||||||
62
extension/examples/react-counter-messaging/components/Counter.js
vendored
Normal file
62
extension/examples/react-counter-messaging/components/Counter.js
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
const withDevTools =
|
||||||
|
// process.env.NODE_ENV === 'development' &&
|
||||||
|
typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__;
|
||||||
|
|
||||||
|
class Counter extends Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = { counter: 0 };
|
||||||
|
|
||||||
|
this.increment = this.increment.bind(this);
|
||||||
|
this.decrement = this.decrement.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
if (withDevTools) {
|
||||||
|
this.devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect();
|
||||||
|
this.unsubscribe = this.devTools.subscribe((message) => {
|
||||||
|
// Implement monitors actions.
|
||||||
|
// For example time traveling:
|
||||||
|
if (
|
||||||
|
message.type === 'DISPATCH' &&
|
||||||
|
message.payload.type === 'JUMP_TO_STATE'
|
||||||
|
) {
|
||||||
|
this.setState(message.state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (withDevTools) {
|
||||||
|
this.unsubscribe(); // Use if you have other subscribers from other components.
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION__.disconnect(); // If there aren't other subscribers.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
increment() {
|
||||||
|
const state = { counter: this.state.counter + 1 };
|
||||||
|
if (withDevTools) this.devTools.send('increment', state);
|
||||||
|
this.setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
decrement() {
|
||||||
|
const state = { counter: this.state.counter - 1 };
|
||||||
|
if (withDevTools) this.devTools.send('decrement', state);
|
||||||
|
this.setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { counter } = this.state;
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
Clicked: {counter} times <button onClick={this.increment}>+</button>{' '}
|
||||||
|
<button onClick={this.decrement}>-</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Counter;
|
||||||
10
extension/examples/react-counter-messaging/index.html
Normal file
10
extension/examples/react-counter-messaging/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>React counter example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="/static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
extension/examples/react-counter-messaging/index.js
vendored
Normal file
5
extension/examples/react-counter-messaging/index.js
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import Counter from './components/Counter';
|
||||||
|
|
||||||
|
render(<Counter />, document.getElementById('root'));
|
||||||
33
extension/examples/react-counter-messaging/package.json
Normal file
33
extension/examples/react-counter-messaging/package.json
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "react-counter-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "React counter example",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack-dev-server --progress"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zalmoxisus/redux-devtools-extension.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/zalmoxisus/redux-devtools-extension/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/zalmoxisus/redux-devtools-extension",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^16.0.0",
|
||||||
|
"react-dom": "^16.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.3.17",
|
||||||
|
"babel-core": "^6.3.17",
|
||||||
|
"babel-loader": "^7.0.0",
|
||||||
|
"babel-preset-es2015": "^6.0.0",
|
||||||
|
"babel-preset-react": "6.3.13",
|
||||||
|
"babel-preset-stage-0": "^6.3.13",
|
||||||
|
"webpack": "^4.0.0",
|
||||||
|
"webpack-cli": "^3.2.0",
|
||||||
|
"webpack-dev-server": "^3.0.0",
|
||||||
|
"webpack-hot-middleware": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
25
extension/examples/react-counter-messaging/webpack.config.js
Normal file
25
extension/examples/react-counter-messaging/webpack.config.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: ['./index'],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/static/',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
port: 4004,
|
||||||
|
},
|
||||||
|
};
|
||||||
4
extension/examples/router/.babelrc
Normal file
4
extension/examples/router/.babelrc
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"],
|
||||||
|
"plugins": ["add-module-exports", "transform-decorators-legacy"]
|
||||||
|
}
|
||||||
25
extension/examples/router/actions/todos.js
Normal file
25
extension/examples/router/actions/todos.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as types from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
export function addTodo(text) {
|
||||||
|
return { type: types.ADD_TODO, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteTodo(id) {
|
||||||
|
return { type: types.DELETE_TODO, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function editTodo(id, text) {
|
||||||
|
return { type: types.EDIT_TODO, id, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeTodo(id) {
|
||||||
|
return { type: types.COMPLETE_TODO, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeAll() {
|
||||||
|
return { type: types.COMPLETE_ALL };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCompleted() {
|
||||||
|
return { type: types.CLEAR_COMPLETED };
|
||||||
|
}
|
||||||
76
extension/examples/router/components/Footer.js
Normal file
76
extension/examples/router/components/Footer.js
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
|
const FILTER_TITLES = {
|
||||||
|
[SHOW_ALL]: 'All',
|
||||||
|
[SHOW_ACTIVE]: 'Active',
|
||||||
|
[SHOW_COMPLETED]: 'Completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
class Footer extends Component {
|
||||||
|
renderTodoCount() {
|
||||||
|
const { activeCount } = this.props;
|
||||||
|
const itemWord = activeCount === 1 ? 'item' : 'items';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="todo-count">
|
||||||
|
<strong>{activeCount || 'No'}</strong> {itemWord} left
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFilterLink(filter) {
|
||||||
|
const title = FILTER_TITLES[filter];
|
||||||
|
const { filter: selectedFilter, onShow } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className={classnames({ selected: filter === selectedFilter })}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => onShow(filter)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderClearButton() {
|
||||||
|
const { completedCount, onClearCompleted } = this.props;
|
||||||
|
if (completedCount > 0) {
|
||||||
|
return (
|
||||||
|
<button className="clear-completed" onClick={onClearCompleted}>
|
||||||
|
Clear completed
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<footer className="footer">
|
||||||
|
{this.renderTodoCount()}
|
||||||
|
<ul className="filters">
|
||||||
|
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
||||||
|
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderClearButton()}
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Footer.propTypes = {
|
||||||
|
completedCount: PropTypes.number.isRequired,
|
||||||
|
activeCount: PropTypes.number.isRequired,
|
||||||
|
filter: PropTypes.string.isRequired,
|
||||||
|
onClearCompleted: PropTypes.func.isRequired,
|
||||||
|
onShow: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
30
extension/examples/router/components/Header.js
Normal file
30
extension/examples/router/components/Header.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React, { PropTypes, Component } from 'react';
|
||||||
|
import TodoTextInput from './TodoTextInput';
|
||||||
|
|
||||||
|
class Header extends Component {
|
||||||
|
handleSave(text) {
|
||||||
|
if (text.length !== 0) {
|
||||||
|
this.props.addTodo(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { path } = this.props;
|
||||||
|
return (
|
||||||
|
<header className="header">
|
||||||
|
<h1 style={{ fontSize: 80 }}>{path}</h1>
|
||||||
|
<TodoTextInput
|
||||||
|
newTodo
|
||||||
|
onSave={this.handleSave.bind(this)}
|
||||||
|
placeholder="What needs to be done?"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
addTodo: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
94
extension/examples/router/components/MainSection.js
Normal file
94
extension/examples/router/components/MainSection.js
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import TodoItem from './TodoItem';
|
||||||
|
import Footer from './Footer';
|
||||||
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
|
const TODO_FILTERS = {
|
||||||
|
[SHOW_ALL]: () => true,
|
||||||
|
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
||||||
|
[SHOW_COMPLETED]: (todo) => todo.completed,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainSection extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = { filter: SHOW_ALL };
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClearCompleted() {
|
||||||
|
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
||||||
|
if (atLeastOneCompleted) {
|
||||||
|
this.props.actions.clearCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShow(filter) {
|
||||||
|
this.setState({ filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToggleAll(completedCount) {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
if (todos.length > 0) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className="toggle-all"
|
||||||
|
type="checkbox"
|
||||||
|
checked={completedCount === todos.length}
|
||||||
|
onChange={actions.completeAll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFooter(completedCount) {
|
||||||
|
const { todos } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
const activeCount = todos.length - completedCount;
|
||||||
|
|
||||||
|
if (todos.length) {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
completedCount={completedCount}
|
||||||
|
activeCount={activeCount}
|
||||||
|
filter={filter}
|
||||||
|
onClearCompleted={this.handleClearCompleted.bind(this)}
|
||||||
|
onShow={this.handleShow.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
|
||||||
|
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
||||||
|
const completedCount = todos.reduce(
|
||||||
|
(count, todo) => (todo.completed ? count + 1 : count),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
{this.renderToggleAll(completedCount)}
|
||||||
|
<ul className="todo-list">
|
||||||
|
{filteredTodos.map((todo) => (
|
||||||
|
<TodoItem key={todo.id} todo={todo} {...actions} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderFooter(completedCount)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MainSection.propTypes = {
|
||||||
|
todos: PropTypes.array.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainSection;
|
||||||
75
extension/examples/router/components/TodoItem.js
Normal file
75
extension/examples/router/components/TodoItem.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import TodoTextInput from './TodoTextInput';
|
||||||
|
|
||||||
|
class TodoItem extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
editing: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoubleClick() {
|
||||||
|
this.setState({ editing: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave(id, text) {
|
||||||
|
if (text.length === 0) {
|
||||||
|
this.props.deleteTodo(id);
|
||||||
|
} else {
|
||||||
|
this.props.editTodo(id, text);
|
||||||
|
}
|
||||||
|
this.setState({ editing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todo, completeTodo, deleteTodo } = this.props;
|
||||||
|
|
||||||
|
let element;
|
||||||
|
if (this.state.editing) {
|
||||||
|
element = (
|
||||||
|
<TodoTextInput
|
||||||
|
text={todo.text}
|
||||||
|
editing={this.state.editing}
|
||||||
|
onSave={(text) => this.handleSave(todo.id, text)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element = (
|
||||||
|
<div className="view">
|
||||||
|
<input
|
||||||
|
className="toggle"
|
||||||
|
type="checkbox"
|
||||||
|
checked={todo.completed}
|
||||||
|
onChange={() => completeTodo(todo.id)}
|
||||||
|
/>
|
||||||
|
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
||||||
|
{todo.text}
|
||||||
|
</label>
|
||||||
|
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames({
|
||||||
|
completed: todo.completed,
|
||||||
|
editing: this.state.editing,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoItem.propTypes = {
|
||||||
|
todo: PropTypes.object.isRequired,
|
||||||
|
editTodo: PropTypes.func.isRequired,
|
||||||
|
deleteTodo: PropTypes.func.isRequired,
|
||||||
|
completeTodo: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoItem;
|
||||||
59
extension/examples/router/components/TodoTextInput.js
Normal file
59
extension/examples/router/components/TodoTextInput.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
class TodoTextInput extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
text: this.props.text || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(e) {
|
||||||
|
const text = e.target.value.trim();
|
||||||
|
if (e.which === 13) {
|
||||||
|
this.props.onSave(text);
|
||||||
|
if (this.props.newTodo) {
|
||||||
|
this.setState({ text: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.setState({ text: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur(e) {
|
||||||
|
if (!this.props.newTodo) {
|
||||||
|
this.props.onSave(e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={classnames({
|
||||||
|
edit: this.props.editing,
|
||||||
|
'new-todo': this.props.newTodo,
|
||||||
|
})}
|
||||||
|
type="text"
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
autoFocus="true"
|
||||||
|
value={this.state.text}
|
||||||
|
onBlur={this.handleBlur.bind(this)}
|
||||||
|
onChange={this.handleChange.bind(this)}
|
||||||
|
onKeyDown={this.handleSubmit.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoTextInput.propTypes = {
|
||||||
|
onSave: PropTypes.func.isRequired,
|
||||||
|
text: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
newTodo: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoTextInput;
|
||||||
6
extension/examples/router/constants/ActionTypes.js
Normal file
6
extension/examples/router/constants/ActionTypes.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const ADD_TODO = 'ADD_TODO';
|
||||||
|
export const DELETE_TODO = 'DELETE_TODO';
|
||||||
|
export const EDIT_TODO = 'EDIT_TODO';
|
||||||
|
export const COMPLETE_TODO = 'COMPLETE_TODO';
|
||||||
|
export const COMPLETE_ALL = 'COMPLETE_ALL';
|
||||||
|
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
|
||||||
3
extension/examples/router/constants/TodoFilters.js
Normal file
3
extension/examples/router/constants/TodoFilters.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const SHOW_ALL = 'show_all';
|
||||||
|
export const SHOW_COMPLETED = 'show_completed';
|
||||||
|
export const SHOW_ACTIVE = 'show_active';
|
||||||
38
extension/examples/router/containers/App.js
Normal file
38
extension/examples/router/containers/App.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Header from '../components/Header';
|
||||||
|
import MainSection from '../components/MainSection';
|
||||||
|
import * as TodoActions from '../actions/todos';
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
const { todos, path, actions } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header addTodo={actions.addTodo} path={path} />
|
||||||
|
<MainSection todos={todos} actions={actions} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
todos: PropTypes.array.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
todos: state.todos,
|
||||||
|
path: state.router.location.pathname,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
21
extension/examples/router/containers/Root.js
Normal file
21
extension/examples/router/containers/Root.js
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { Route, Redirect } from 'react-router';
|
||||||
|
import { ReduxRouter } from 'redux-router';
|
||||||
|
import Wrapper from './Wrapper';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
class Root extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ReduxRouter>
|
||||||
|
<Redirect from="/" to="Standard Todo" />
|
||||||
|
<Route path="/" component={Wrapper}>
|
||||||
|
<Route path="/:id" component={App} />
|
||||||
|
</Route>
|
||||||
|
</ReduxRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Root;
|
||||||
68
extension/examples/router/containers/Wrapper.js
Normal file
68
extension/examples/router/containers/Wrapper.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { pushState } from 'redux-router';
|
||||||
|
import { Route, Link } from 'react-router';
|
||||||
|
import * as TodoActions from '../actions/todos';
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
pushState: bindActionCreators(pushState, dispatch),
|
||||||
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@connect((state) => ({}), mapDispatchToProps)
|
||||||
|
class Wrapper extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const { actions, pushState } = this.props;
|
||||||
|
const path = event.target.innerText;
|
||||||
|
|
||||||
|
pushState(null, path);
|
||||||
|
console.log('Navigate to', path);
|
||||||
|
|
||||||
|
if (this.timeout) clearInterval(this.timeout);
|
||||||
|
if (path === 'AutoTodo') {
|
||||||
|
console.log('!');
|
||||||
|
this.timeout = setInterval(() => {
|
||||||
|
actions.addTodo('Auto generated task');
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
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>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wrapper;
|
||||||
10
extension/examples/router/index.html
Normal file
10
extension/examples/router/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Redux TodoMVC example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="todoapp" id="root"></div>
|
||||||
|
<script src="/static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
extension/examples/router/index.js
Normal file
16
extension/examples/router/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'babel-polyfill';
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import Root from './containers/Root';
|
||||||
|
import configureStore from './store/configureStore';
|
||||||
|
import 'todomvc-app-css/index.css';
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<Root />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
53
extension/examples/router/package.json
Normal file
53
extension/examples/router/package.json
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
"name": "redux-todomvc-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Redux TodoMVC example",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"test": "NODE_ENV=test mocha --recursive --compilers js:babel-core/register --require ./test/setup.js",
|
||||||
|
"test:watch": "npm test -- --watch"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rackt/redux.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/rackt/redux/issues"
|
||||||
|
},
|
||||||
|
"homepage": "http://rackt.github.io/redux",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.1.2",
|
||||||
|
"history": "^1.13.1",
|
||||||
|
"react": "^0.14.0",
|
||||||
|
"react-dom": "^0.14.0",
|
||||||
|
"react-redux": "^4.0.0",
|
||||||
|
"react-router": "^1.0.2",
|
||||||
|
"redux": "^3.0.0",
|
||||||
|
"redux-router": "^1.0.0-beta5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-core": "^6.3.15",
|
||||||
|
"babel-loader": "^6.2.0",
|
||||||
|
"babel-plugin-add-module-exports": "^0.1.1",
|
||||||
|
"babel-plugin-react-transform": "^2.0.0-beta1",
|
||||||
|
"babel-plugin-transform-decorators-legacy": "^1.2.0",
|
||||||
|
"babel-polyfill": "^6.3.14",
|
||||||
|
"babel-preset-es2015": "^6.3.13",
|
||||||
|
"babel-preset-react": "^6.3.13",
|
||||||
|
"babel-preset-stage-0": "^6.3.13",
|
||||||
|
"expect": "^1.8.0",
|
||||||
|
"express": "^4.13.3",
|
||||||
|
"jsdom": "^5.6.1",
|
||||||
|
"mocha": "^2.2.5",
|
||||||
|
"node-libs-browser": "^0.5.2",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
|
"react-addons-test-utils": "^0.14.0",
|
||||||
|
"react-transform-hmr": "^1.0.0",
|
||||||
|
"style-loader": "^0.12.3",
|
||||||
|
"todomvc-app-css": "^2.0.1",
|
||||||
|
"webpack": "^1.9.11",
|
||||||
|
"webpack-dev-middleware": "^1.2.0",
|
||||||
|
"webpack-hot-middleware": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
extension/examples/router/reducers/index.js
Normal file
10
extension/examples/router/reducers/index.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { routerStateReducer } from 'redux-router';
|
||||||
|
import todos from './todos';
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
todos,
|
||||||
|
router: routerStateReducer,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default rootReducer;
|
||||||
61
extension/examples/router/reducers/todos.js
Normal file
61
extension/examples/router/reducers/todos.js
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import {
|
||||||
|
ADD_TODO,
|
||||||
|
DELETE_TODO,
|
||||||
|
EDIT_TODO,
|
||||||
|
COMPLETE_TODO,
|
||||||
|
COMPLETE_ALL,
|
||||||
|
CLEAR_COMPLETED,
|
||||||
|
} from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
const initialState = [
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function todos(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_TODO:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
|
||||||
|
completed: false,
|
||||||
|
text: action.text,
|
||||||
|
},
|
||||||
|
...state,
|
||||||
|
];
|
||||||
|
|
||||||
|
case DELETE_TODO:
|
||||||
|
return state.filter((todo) => todo.id !== action.id);
|
||||||
|
|
||||||
|
case EDIT_TODO:
|
||||||
|
return state.map((todo) =>
|
||||||
|
todo.id === action.id
|
||||||
|
? Object.assign({}, todo, { text: action.text })
|
||||||
|
: todo,
|
||||||
|
);
|
||||||
|
|
||||||
|
case COMPLETE_TODO:
|
||||||
|
return state.map((todo) =>
|
||||||
|
todo.id === action.id
|
||||||
|
? Object.assign({}, todo, { completed: !todo.completed })
|
||||||
|
: todo,
|
||||||
|
);
|
||||||
|
|
||||||
|
case COMPLETE_ALL:
|
||||||
|
const areAllMarked = state.every((todo) => todo.completed);
|
||||||
|
return state.map((todo) =>
|
||||||
|
Object.assign({}, todo, {
|
||||||
|
completed: !areAllMarked,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
case CLEAR_COMPLETED:
|
||||||
|
return state.filter((todo) => todo.completed === false);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
extension/examples/router/server.js
Normal file
32
extension/examples/router/server.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
var webpack = require('webpack');
|
||||||
|
var webpackDevMiddleware = require('webpack-dev-middleware');
|
||||||
|
var webpackHotMiddleware = require('webpack-hot-middleware');
|
||||||
|
var config = require('./webpack.config');
|
||||||
|
|
||||||
|
var app = new require('express')();
|
||||||
|
var port = 4002;
|
||||||
|
|
||||||
|
var compiler = webpack(config);
|
||||||
|
app.use(
|
||||||
|
webpackDevMiddleware(compiler, {
|
||||||
|
noInfo: true,
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.use(webpackHotMiddleware(compiler));
|
||||||
|
|
||||||
|
app.get('/', function (req, res) {
|
||||||
|
res.sendFile(__dirname + '/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, function (error) {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
'==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.',
|
||||||
|
port,
|
||||||
|
port,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
28
extension/examples/router/store/configureStore.js
Normal file
28
extension/examples/router/store/configureStore.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { createStore, compose } from 'redux';
|
||||||
|
import {
|
||||||
|
reduxReactRouter,
|
||||||
|
routerStateReducer,
|
||||||
|
ReduxRouter,
|
||||||
|
} from 'redux-router';
|
||||||
|
//import createHistory from 'history/lib/createBrowserHistory';
|
||||||
|
import createHistory from 'history/lib/createHashHistory';
|
||||||
|
import rootReducer from '../reducers';
|
||||||
|
|
||||||
|
export default function configureStore(initialState) {
|
||||||
|
let finalCreateStore = compose(
|
||||||
|
reduxReactRouter({ createHistory }),
|
||||||
|
global.devToolsExtension ? global.devToolsExtension() : (f) => f,
|
||||||
|
)(createStore);
|
||||||
|
|
||||||
|
const store = finalCreateStore(rootReducer, initialState);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
// Enable Webpack hot module replacement for reducers
|
||||||
|
module.hot.accept('../reducers', () => {
|
||||||
|
const nextReducer = require('../reducers');
|
||||||
|
store.replaceReducer(nextReducer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
||||||
46
extension/examples/router/test/actions/todos.spec.js
Normal file
46
extension/examples/router/test/actions/todos.spec.js
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import * as types from '../../constants/ActionTypes';
|
||||||
|
import * as actions from '../../actions/todos';
|
||||||
|
|
||||||
|
describe('todo actions', () => {
|
||||||
|
it('addTodo should create ADD_TODO action', () => {
|
||||||
|
expect(actions.addTodo('Use Redux')).toEqual({
|
||||||
|
type: types.ADD_TODO,
|
||||||
|
text: 'Use Redux',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deleteTodo should create DELETE_TODO action', () => {
|
||||||
|
expect(actions.deleteTodo(1)).toEqual({
|
||||||
|
type: types.DELETE_TODO,
|
||||||
|
id: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('editTodo should create EDIT_TODO action', () => {
|
||||||
|
expect(actions.editTodo(1, 'Use Redux everywhere')).toEqual({
|
||||||
|
type: types.EDIT_TODO,
|
||||||
|
id: 1,
|
||||||
|
text: 'Use Redux everywhere',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('completeTodo should create COMPLETE_TODO action', () => {
|
||||||
|
expect(actions.completeTodo(1)).toEqual({
|
||||||
|
type: types.COMPLETE_TODO,
|
||||||
|
id: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('completeAll should create COMPLETE_ALL action', () => {
|
||||||
|
expect(actions.completeAll()).toEqual({
|
||||||
|
type: types.COMPLETE_ALL,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clearCompleted should create CLEAR_COMPLETED action', () => {
|
||||||
|
expect(actions.clearCompleted('Use Redux')).toEqual({
|
||||||
|
type: types.CLEAR_COMPLETED,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
108
extension/examples/router/test/components/Footer.spec.js
Normal file
108
extension/examples/router/test/components/Footer.spec.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import Footer from '../../components/Footer';
|
||||||
|
import { SHOW_ALL, SHOW_ACTIVE } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
|
function setup(propOverrides) {
|
||||||
|
const props = Object.assign(
|
||||||
|
{
|
||||||
|
completedCount: 0,
|
||||||
|
activeCount: 0,
|
||||||
|
filter: SHOW_ALL,
|
||||||
|
onClearCompleted: expect.createSpy(),
|
||||||
|
onShow: expect.createSpy(),
|
||||||
|
},
|
||||||
|
propOverrides,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderer = TestUtils.createRenderer();
|
||||||
|
renderer.render(<Footer {...props} />);
|
||||||
|
const output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
output: output,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextContent(elem) {
|
||||||
|
const children = Array.isArray(elem.props.children)
|
||||||
|
? elem.props.children
|
||||||
|
: [elem.props.children];
|
||||||
|
|
||||||
|
return children.reduce(function concatText(out, child) {
|
||||||
|
// Children are either elements or text strings
|
||||||
|
return out + (child.props ? getTextContent(child) : child);
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('Footer', () => {
|
||||||
|
it('should render container', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
expect(output.type).toBe('footer');
|
||||||
|
expect(output.props.className).toBe('footer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display active count when 0', () => {
|
||||||
|
const { output } = setup({ activeCount: 0 });
|
||||||
|
const [count] = output.props.children;
|
||||||
|
expect(getTextContent(count)).toBe('No items left');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display active count when above 0', () => {
|
||||||
|
const { output } = setup({ activeCount: 1 });
|
||||||
|
const [count] = output.props.children;
|
||||||
|
expect(getTextContent(count)).toBe('1 item left');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render filters', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
const [, filters] = output.props.children;
|
||||||
|
expect(filters.type).toBe('ul');
|
||||||
|
expect(filters.props.className).toBe('filters');
|
||||||
|
expect(filters.props.children.length).toBe(3);
|
||||||
|
filters.props.children.forEach(function checkFilter(filter, i) {
|
||||||
|
expect(filter.type).toBe('li');
|
||||||
|
const a = filter.props.children;
|
||||||
|
expect(a.props.className).toBe(i === 0 ? 'selected' : '');
|
||||||
|
expect(a.props.children).toBe(
|
||||||
|
{
|
||||||
|
0: 'All',
|
||||||
|
1: 'Active',
|
||||||
|
2: 'Completed',
|
||||||
|
}[i],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onShow when a filter is clicked', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const [, filters] = output.props.children;
|
||||||
|
const filterLink = filters.props.children[1].props.children;
|
||||||
|
filterLink.props.onClick({});
|
||||||
|
expect(props.onShow).toHaveBeenCalledWith(SHOW_ACTIVE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldnt show clear button when no completed todos', () => {
|
||||||
|
const { output } = setup({ completedCount: 0 });
|
||||||
|
const [, , clear] = output.props.children;
|
||||||
|
expect(clear).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render clear button when completed todos', () => {
|
||||||
|
const { output } = setup({ completedCount: 1 });
|
||||||
|
const [, , clear] = output.props.children;
|
||||||
|
expect(clear.type).toBe('button');
|
||||||
|
expect(clear.props.children).toBe('Clear completed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onClearCompleted on clear button click', () => {
|
||||||
|
const { output, props } = setup({ completedCount: 1 });
|
||||||
|
const [, , clear] = output.props.children;
|
||||||
|
clear.props.onClick({});
|
||||||
|
expect(props.onClearCompleted).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
50
extension/examples/router/test/components/Header.spec.js
Normal file
50
extension/examples/router/test/components/Header.spec.js
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import Header from '../../components/Header';
|
||||||
|
import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
const props = {
|
||||||
|
addTodo: expect.createSpy(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderer = TestUtils.createRenderer();
|
||||||
|
renderer.render(<Header {...props} />);
|
||||||
|
const output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
output: output,
|
||||||
|
renderer: renderer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('Header', () => {
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
|
||||||
|
expect(output.type).toBe('header');
|
||||||
|
expect(output.props.className).toBe('header');
|
||||||
|
|
||||||
|
const [h1, input] = output.props.children;
|
||||||
|
|
||||||
|
expect(h1.type).toBe('h1');
|
||||||
|
expect(h1.props.children).toBe('todos');
|
||||||
|
|
||||||
|
expect(input.type).toBe(TodoTextInput);
|
||||||
|
expect(input.props.newTodo).toBe(true);
|
||||||
|
expect(input.props.placeholder).toBe('What needs to be done?');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call call addTodo if length of text is greater than 0', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const input = output.props.children[1];
|
||||||
|
input.props.onSave('');
|
||||||
|
expect(props.addTodo.calls.length).toBe(0);
|
||||||
|
input.props.onSave('Use Redux');
|
||||||
|
expect(props.addTodo.calls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
150
extension/examples/router/test/components/MainSection.spec.js
Normal file
150
extension/examples/router/test/components/MainSection.spec.js
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import MainSection from '../../components/MainSection';
|
||||||
|
import TodoItem from '../../components/TodoItem';
|
||||||
|
import Footer from '../../components/Footer';
|
||||||
|
import { SHOW_ALL, SHOW_COMPLETED } from '../../constants/TodoFilters';
|
||||||
|
|
||||||
|
function setup(propOverrides) {
|
||||||
|
const props = Object.assign(
|
||||||
|
{
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
actions: {
|
||||||
|
editTodo: expect.createSpy(),
|
||||||
|
deleteTodo: expect.createSpy(),
|
||||||
|
completeTodo: expect.createSpy(),
|
||||||
|
completeAll: expect.createSpy(),
|
||||||
|
clearCompleted: expect.createSpy(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
propOverrides,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderer = TestUtils.createRenderer();
|
||||||
|
renderer.render(<MainSection {...props} />);
|
||||||
|
const output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
output: output,
|
||||||
|
renderer: renderer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('MainSection', () => {
|
||||||
|
it('should render container', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
expect(output.type).toBe('section');
|
||||||
|
expect(output.props.className).toBe('main');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggle all input', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
const [toggle] = output.props.children;
|
||||||
|
expect(toggle.type).toBe('input');
|
||||||
|
expect(toggle.props.type).toBe('checkbox');
|
||||||
|
expect(toggle.props.checked).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be checked if all todos completed', () => {
|
||||||
|
const { output } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: true,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const [toggle] = output.props.children;
|
||||||
|
expect(toggle.props.checked).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call completeAll on change', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const [toggle] = output.props.children;
|
||||||
|
toggle.props.onChange({});
|
||||||
|
expect(props.actions.completeAll).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('footer', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
const [, , footer] = output.props.children;
|
||||||
|
expect(footer.type).toBe(Footer);
|
||||||
|
expect(footer.props.completedCount).toBe(1);
|
||||||
|
expect(footer.props.activeCount).toBe(1);
|
||||||
|
expect(footer.props.filter).toBe(SHOW_ALL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onShow should set the filter', () => {
|
||||||
|
const { output, renderer } = setup();
|
||||||
|
const [, , footer] = output.props.children;
|
||||||
|
footer.props.onShow(SHOW_COMPLETED);
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
const [, , updatedFooter] = updated.props.children;
|
||||||
|
expect(updatedFooter.props.filter).toBe(SHOW_COMPLETED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onClearCompleted should call clearCompleted', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const [, , footer] = output.props.children;
|
||||||
|
footer.props.onClearCompleted();
|
||||||
|
expect(props.actions.clearCompleted).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onClearCompleted shouldnt call clearCompleted if no todos completed', () => {
|
||||||
|
const { output, props } = setup({
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const [, , footer] = output.props.children;
|
||||||
|
footer.props.onClearCompleted();
|
||||||
|
expect(props.actions.clearCompleted.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('todo list', () => {
|
||||||
|
it('should render', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const [, list] = output.props.children;
|
||||||
|
expect(list.type).toBe('ul');
|
||||||
|
expect(list.props.children.length).toBe(2);
|
||||||
|
list.props.children.forEach((item, i) => {
|
||||||
|
expect(item.type).toBe(TodoItem);
|
||||||
|
expect(item.props.todo).toBe(props.todos[i]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter items', () => {
|
||||||
|
const { output, renderer, props } = setup();
|
||||||
|
const [, , footer] = output.props.children;
|
||||||
|
footer.props.onShow(SHOW_COMPLETED);
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
const [, updatedList] = updated.props.children;
|
||||||
|
expect(updatedList.props.children.length).toBe(1);
|
||||||
|
expect(updatedList.props.children[0].props.todo).toBe(props.todos[1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
118
extension/examples/router/test/components/TodoItem.spec.js
Normal file
118
extension/examples/router/test/components/TodoItem.spec.js
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import TodoItem from '../../components/TodoItem';
|
||||||
|
import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
|
function setup(editing = false) {
|
||||||
|
const props = {
|
||||||
|
todo: {
|
||||||
|
id: 0,
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
editTodo: expect.createSpy(),
|
||||||
|
deleteTodo: expect.createSpy(),
|
||||||
|
completeTodo: expect.createSpy(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
|
renderer.render(<TodoItem {...props} />);
|
||||||
|
|
||||||
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
const label = output.props.children.props.children[1];
|
||||||
|
label.props.onDoubleClick({});
|
||||||
|
output = renderer.getRenderOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
output: output,
|
||||||
|
renderer: renderer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('TodoItem', () => {
|
||||||
|
it('initial render', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
|
||||||
|
expect(output.type).toBe('li');
|
||||||
|
expect(output.props.className).toBe('');
|
||||||
|
|
||||||
|
const div = output.props.children;
|
||||||
|
|
||||||
|
expect(div.type).toBe('div');
|
||||||
|
expect(div.props.className).toBe('view');
|
||||||
|
|
||||||
|
const [input, label, button] = div.props.children;
|
||||||
|
|
||||||
|
expect(input.type).toBe('input');
|
||||||
|
expect(input.props.checked).toBe(false);
|
||||||
|
|
||||||
|
expect(label.type).toBe('label');
|
||||||
|
expect(label.props.children).toBe('Use Redux');
|
||||||
|
|
||||||
|
expect(button.type).toBe('button');
|
||||||
|
expect(button.props.className).toBe('destroy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('input onChange should call completeTodo', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const input = output.props.children.props.children[0];
|
||||||
|
input.props.onChange({});
|
||||||
|
expect(props.completeTodo).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('button onClick should call deleteTodo', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
const button = output.props.children.props.children[2];
|
||||||
|
button.props.onClick({});
|
||||||
|
expect(props.deleteTodo).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('label onDoubleClick should put component in edit state', () => {
|
||||||
|
const { output, renderer } = setup();
|
||||||
|
const label = output.props.children.props.children[1];
|
||||||
|
label.props.onDoubleClick({});
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
expect(updated.type).toBe('li');
|
||||||
|
expect(updated.props.className).toBe('editing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('edit state render', () => {
|
||||||
|
const { output } = setup(true);
|
||||||
|
|
||||||
|
expect(output.type).toBe('li');
|
||||||
|
expect(output.props.className).toBe('editing');
|
||||||
|
|
||||||
|
const input = output.props.children;
|
||||||
|
expect(input.type).toBe(TodoTextInput);
|
||||||
|
expect(input.props.text).toBe('Use Redux');
|
||||||
|
expect(input.props.editing).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TodoTextInput onSave should call editTodo', () => {
|
||||||
|
const { output, props } = setup(true);
|
||||||
|
output.props.children.props.onSave('Use Redux');
|
||||||
|
expect(props.editTodo).toHaveBeenCalledWith(0, 'Use Redux');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TodoTextInput onSave should call deleteTodo if text is empty', () => {
|
||||||
|
const { output, props } = setup(true);
|
||||||
|
output.props.children.props.onSave('');
|
||||||
|
expect(props.deleteTodo).toHaveBeenCalledWith(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TodoTextInput onSave should exit component from edit state', () => {
|
||||||
|
const { output, renderer } = setup(true);
|
||||||
|
output.props.children.props.onSave('Use Redux');
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
expect(updated.type).toBe('li');
|
||||||
|
expect(updated.props.className).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import React from 'react';
|
||||||
|
import TestUtils from 'react-addons-test-utils';
|
||||||
|
import TodoTextInput from '../../components/TodoTextInput';
|
||||||
|
|
||||||
|
function setup(propOverrides) {
|
||||||
|
const props = Object.assign(
|
||||||
|
{
|
||||||
|
onSave: expect.createSpy(),
|
||||||
|
text: 'Use Redux',
|
||||||
|
placeholder: 'What needs to be done?',
|
||||||
|
editing: false,
|
||||||
|
newTodo: false,
|
||||||
|
},
|
||||||
|
propOverrides,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderer = TestUtils.createRenderer();
|
||||||
|
|
||||||
|
renderer.render(<TodoTextInput {...props} />);
|
||||||
|
|
||||||
|
let output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
output = renderer.getRenderOutput();
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: props,
|
||||||
|
output: output,
|
||||||
|
renderer: renderer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('components', () => {
|
||||||
|
describe('TodoTextInput', () => {
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const { output } = setup();
|
||||||
|
expect(output.props.placeholder).toEqual('What needs to be done?');
|
||||||
|
expect(output.props.value).toEqual('Use Redux');
|
||||||
|
expect(output.props.className).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly when editing=true', () => {
|
||||||
|
const { output } = setup({ editing: true });
|
||||||
|
expect(output.props.className).toEqual('edit');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly when newTodo=true', () => {
|
||||||
|
const { output } = setup({ newTodo: true });
|
||||||
|
expect(output.props.className).toEqual('new-todo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update value on change', () => {
|
||||||
|
const { output, renderer } = setup();
|
||||||
|
output.props.onChange({ target: { value: 'Use Radox' } });
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
expect(updated.props.value).toEqual('Use Radox');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onSave on return key press', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } });
|
||||||
|
expect(props.onSave).toHaveBeenCalledWith('Use Redux');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset state on return key press if newTodo', () => {
|
||||||
|
const { output, renderer } = setup({ newTodo: true });
|
||||||
|
output.props.onKeyDown({ which: 13, target: { value: 'Use Redux' } });
|
||||||
|
const updated = renderer.getRenderOutput();
|
||||||
|
expect(updated.props.value).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call onSave on blur', () => {
|
||||||
|
const { output, props } = setup();
|
||||||
|
output.props.onBlur({ target: { value: 'Use Redux' } });
|
||||||
|
expect(props.onSave).toHaveBeenCalledWith('Use Redux');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldnt call onSave on blur if newTodo', () => {
|
||||||
|
const { output, props } = setup({ newTodo: true });
|
||||||
|
output.props.onBlur({ target: { value: 'Use Redux' } });
|
||||||
|
expect(props.onSave.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
325
extension/examples/router/test/reducers/todos.spec.js
Normal file
325
extension/examples/router/test/reducers/todos.spec.js
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
import expect from 'expect';
|
||||||
|
import todos from '../../reducers/todos';
|
||||||
|
import * as types from '../../constants/ActionTypes';
|
||||||
|
|
||||||
|
describe('todos reducer', () => {
|
||||||
|
it('should handle initial state', () => {
|
||||||
|
expect(todos(undefined, {})).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle ADD_TODO', () => {
|
||||||
|
expect(
|
||||||
|
todos([], {
|
||||||
|
type: types.ADD_TODO,
|
||||||
|
text: 'Run the tests',
|
||||||
|
}),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.ADD_TODO,
|
||||||
|
text: 'Run the tests',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.ADD_TODO,
|
||||||
|
text: 'Fix the tests',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Fix the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle DELETE_TODO', () => {
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.DELETE_TODO,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle EDIT_TODO', () => {
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.EDIT_TODO,
|
||||||
|
text: 'Fix the tests',
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Fix the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle COMPLETE_TODO', () => {
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.COMPLETE_TODO,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle COMPLETE_ALL', () => {
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.COMPLETE_ALL,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: true,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Unmark if all todos are currently completed
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: true,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.COMPLETE_ALL,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle CLEAR_COMPLETED', () => {
|
||||||
|
expect(
|
||||||
|
todos(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'Run the tests',
|
||||||
|
completed: true,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
type: types.CLEAR_COMPLETED,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Use Redux',
|
||||||
|
completed: false,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not generate duplicate ids after CLEAR_COMPLETED', () => {
|
||||||
|
expect(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: types.COMPLETE_TODO,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: types.CLEAR_COMPLETED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: types.ADD_TODO,
|
||||||
|
text: 'Write more tests',
|
||||||
|
},
|
||||||
|
].reduce(todos, [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
completed: false,
|
||||||
|
text: 'Use Redux',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
completed: false,
|
||||||
|
text: 'Write tests',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
text: 'Write more tests',
|
||||||
|
completed: false,
|
||||||
|
id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Write tests',
|
||||||
|
completed: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
5
extension/examples/router/test/setup.js
Normal file
5
extension/examples/router/test/setup.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { jsdom } from 'jsdom';
|
||||||
|
|
||||||
|
global.document = jsdom('<!doctype html><html><body></body></html>');
|
||||||
|
global.window = document.defaultView;
|
||||||
|
global.navigator = global.window.navigator;
|
||||||
32
extension/examples/router/webpack.config.js
Normal file
32
extension/examples/router/webpack.config.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
devtool: 'cheap-module-eval-source-map',
|
||||||
|
entry: ['webpack-hot-middleware/client', './index'],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/static/',
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.OccurenceOrderPlugin(),
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
new webpack.NoErrorsPlugin(),
|
||||||
|
],
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
include: __dirname,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css?$/,
|
||||||
|
loaders: ['style', 'raw'],
|
||||||
|
include: __dirname,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
3
extension/examples/saga-counter/.babelrc
Normal file
3
extension/examples/saga-counter/.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"]
|
||||||
|
}
|
||||||
13
extension/examples/saga-counter/index.html
Normal file
13
extension/examples/saga-counter/index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Redux Saga Counter example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
37
extension/examples/saga-counter/package.json
Normal file
37
extension/examples/saga-counter/package.json
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
{
|
||||||
|
"name": "redux-counter-example",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Redux counter example",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack-dev-server --progress"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/rackt/redux.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/rackt/redux/issues"
|
||||||
|
},
|
||||||
|
"homepage": "http://rackt.github.io/redux",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^16.0.0",
|
||||||
|
"react-dom": "^16.0.0",
|
||||||
|
"react-redux": "^6.0.0",
|
||||||
|
"redux": "^4.0.0",
|
||||||
|
"redux-saga": "^0.10.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.3.17",
|
||||||
|
"babel-core": "^6.3.17",
|
||||||
|
"babel-loader": "^7.0.0",
|
||||||
|
"babel-preset-es2015": "^6.0.0",
|
||||||
|
"babel-preset-react": "6.3.13",
|
||||||
|
"babel-preset-stage-0": "^6.3.13",
|
||||||
|
"webpack": "^4.0.0",
|
||||||
|
"webpack-cli": "^3.2.0",
|
||||||
|
"webpack-dev-server": "^3.1.0",
|
||||||
|
"webpack-hot-middleware": "^2.24.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
27
extension/examples/saga-counter/src/components/Counter.js
Normal file
27
extension/examples/saga-counter/src/components/Counter.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
const Counter = ({
|
||||||
|
value,
|
||||||
|
onIncrement,
|
||||||
|
onIncrementAsync,
|
||||||
|
onDecrement,
|
||||||
|
onIncrementIfOdd,
|
||||||
|
}) => (
|
||||||
|
<p>
|
||||||
|
Clicked: {value} times <button onClick={onIncrement}>+</button>{' '}
|
||||||
|
<button onClick={onDecrement}>-</button>{' '}
|
||||||
|
<button onClick={onIncrementIfOdd}>Increment if odd</button>{' '}
|
||||||
|
<button onClick={onIncrementAsync}>Increment async</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
Counter.propTypes = {
|
||||||
|
value: PropTypes.number.isRequired,
|
||||||
|
onIncrement: PropTypes.func.isRequired,
|
||||||
|
onDecrement: PropTypes.func.isRequired,
|
||||||
|
onIncrementAsync: PropTypes.func.isRequired,
|
||||||
|
onIncrementIfOdd: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Counter;
|
||||||
43
extension/examples/saga-counter/src/main.js
Normal file
43
extension/examples/saga-counter/src/main.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'babel-polyfill';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
// import sagaMonitor from './sagaMonitor'
|
||||||
|
|
||||||
|
import Counter from './components/Counter';
|
||||||
|
import reducer from './reducers';
|
||||||
|
import rootSaga from './sagas';
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware(/* {sagaMonitor} */);
|
||||||
|
const composeEnhancers =
|
||||||
|
(window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
|
||||||
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||||
|
trace: true,
|
||||||
|
traceLimit: 25,
|
||||||
|
})) ||
|
||||||
|
compose;
|
||||||
|
const store = createStore(
|
||||||
|
reducer,
|
||||||
|
composeEnhancers(applyMiddleware(sagaMiddleware)),
|
||||||
|
);
|
||||||
|
sagaMiddleware.run(rootSaga);
|
||||||
|
|
||||||
|
const action = (type) => store.dispatch({ type });
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
ReactDOM.render(
|
||||||
|
<Counter
|
||||||
|
value={store.getState()}
|
||||||
|
onIncrement={() => action('INCREMENT')}
|
||||||
|
onDecrement={() => action('DECREMENT')}
|
||||||
|
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
|
||||||
|
onIncrementAsync={() => action('INCREMENT_ASYNC')}
|
||||||
|
/>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
store.subscribe(render);
|
||||||
12
extension/examples/saga-counter/src/reducers/index.js
Normal file
12
extension/examples/saga-counter/src/reducers/index.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default function counter(state = 0, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'INCREMENT':
|
||||||
|
return state + 1;
|
||||||
|
case 'INCREMENT_IF_ODD':
|
||||||
|
return state % 2 !== 0 ? state + 1 : state;
|
||||||
|
case 'DECREMENT':
|
||||||
|
return state - 1;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
extension/examples/saga-counter/src/sagas/index.js
Normal file
14
extension/examples/saga-counter/src/sagas/index.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* eslint-disable no-constant-condition */
|
||||||
|
|
||||||
|
import { takeEvery } from 'redux-saga';
|
||||||
|
import { put, call } from 'redux-saga/effects';
|
||||||
|
import { delay } from 'redux-saga';
|
||||||
|
|
||||||
|
export function* incrementAsync() {
|
||||||
|
yield call(delay, 1000);
|
||||||
|
yield put({ type: 'INCREMENT' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function* rootSaga() {
|
||||||
|
yield* takeEvery('INCREMENT_ASYNC', incrementAsync);
|
||||||
|
}
|
||||||
25
extension/examples/saga-counter/webpack.config.js
Normal file
25
extension/examples/saga-counter/webpack.config.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
var path = require('path');
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'source-map',
|
||||||
|
entry: [path.join(__dirname, 'src', 'main')],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: '/static/',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
port: 4003,
|
||||||
|
},
|
||||||
|
};
|
||||||
3
extension/examples/todomvc/.babelrc
Normal file
3
extension/examples/todomvc/.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "react"]
|
||||||
|
}
|
||||||
1
extension/examples/todomvc/actions/index.js
Normal file
1
extension/examples/todomvc/actions/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './todos';
|
||||||
25
extension/examples/todomvc/actions/todos.js
Normal file
25
extension/examples/todomvc/actions/todos.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import * as types from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
export function addTodo(text) {
|
||||||
|
return { type: types.ADD_TODO, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteTodo(id) {
|
||||||
|
return { type: types.DELETE_TODO, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function editTodo(id, text) {
|
||||||
|
return { type: types.EDIT_TODO, id, text };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeTodo(id) {
|
||||||
|
return { type: types.COMPLETE_TODO, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function completeAll() {
|
||||||
|
return { type: types.COMPLETE_ALL };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCompleted() {
|
||||||
|
return { type: types.CLEAR_COMPLETED };
|
||||||
|
}
|
||||||
77
extension/examples/todomvc/components/Footer.js
Normal file
77
extension/examples/todomvc/components/Footer.js
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
|
const FILTER_TITLES = {
|
||||||
|
[SHOW_ALL]: 'All',
|
||||||
|
[SHOW_ACTIVE]: 'Active',
|
||||||
|
[SHOW_COMPLETED]: 'Completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
class Footer extends Component {
|
||||||
|
renderTodoCount() {
|
||||||
|
const { activeCount } = this.props;
|
||||||
|
const itemWord = activeCount === 1 ? 'item' : 'items';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="todo-count">
|
||||||
|
<strong>{activeCount || 'No'}</strong> {itemWord} left
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFilterLink(filter) {
|
||||||
|
const title = FILTER_TITLES[filter];
|
||||||
|
const { filter: selectedFilter, onShow } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
className={classnames({ selected: filter === selectedFilter })}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => onShow(filter)}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderClearButton() {
|
||||||
|
const { completedCount, onClearCompleted } = this.props;
|
||||||
|
if (completedCount > 0) {
|
||||||
|
return (
|
||||||
|
<button className="clear-completed" onClick={onClearCompleted}>
|
||||||
|
Clear completed
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<footer className="footer">
|
||||||
|
{this.renderTodoCount()}
|
||||||
|
<ul className="filters">
|
||||||
|
{[SHOW_ALL, SHOW_ACTIVE, SHOW_COMPLETED].map((filter) => (
|
||||||
|
<li key={filter}>{this.renderFilterLink(filter)}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderClearButton()}
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Footer.propTypes = {
|
||||||
|
completedCount: PropTypes.number.isRequired,
|
||||||
|
activeCount: PropTypes.number.isRequired,
|
||||||
|
filter: PropTypes.string.isRequired,
|
||||||
|
onClearCompleted: PropTypes.func.isRequired,
|
||||||
|
onShow: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
30
extension/examples/todomvc/components/Header.js
Normal file
30
extension/examples/todomvc/components/Header.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import TodoTextInput from './TodoTextInput';
|
||||||
|
|
||||||
|
class Header extends Component {
|
||||||
|
handleSave(text) {
|
||||||
|
if (text.length !== 0) {
|
||||||
|
this.props.addTodo(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<header className="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<TodoTextInput
|
||||||
|
newTodo
|
||||||
|
onSave={this.handleSave.bind(this)}
|
||||||
|
placeholder="What needs to be done?"
|
||||||
|
/>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
addTodo: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Header;
|
||||||
95
extension/examples/todomvc/components/MainSection.js
Normal file
95
extension/examples/todomvc/components/MainSection.js
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import TodoItem from './TodoItem';
|
||||||
|
import Footer from './Footer';
|
||||||
|
import {
|
||||||
|
SHOW_ALL,
|
||||||
|
SHOW_COMPLETED,
|
||||||
|
SHOW_ACTIVE,
|
||||||
|
} from '../constants/TodoFilters';
|
||||||
|
|
||||||
|
const TODO_FILTERS = {
|
||||||
|
[SHOW_ALL]: () => true,
|
||||||
|
[SHOW_ACTIVE]: (todo) => !todo.completed,
|
||||||
|
[SHOW_COMPLETED]: (todo) => todo.completed,
|
||||||
|
};
|
||||||
|
|
||||||
|
class MainSection extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = { filter: SHOW_ALL };
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClearCompleted() {
|
||||||
|
const atLeastOneCompleted = this.props.todos.some((todo) => todo.completed);
|
||||||
|
if (atLeastOneCompleted) {
|
||||||
|
this.props.actions.clearCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleShow(filter) {
|
||||||
|
this.setState({ filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToggleAll(completedCount) {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
if (todos.length > 0) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className="toggle-all"
|
||||||
|
type="checkbox"
|
||||||
|
checked={completedCount === todos.length}
|
||||||
|
onChange={actions.completeAll}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFooter(completedCount) {
|
||||||
|
const { todos } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
const activeCount = todos.length - completedCount;
|
||||||
|
|
||||||
|
if (todos.length) {
|
||||||
|
return (
|
||||||
|
<Footer
|
||||||
|
completedCount={completedCount}
|
||||||
|
activeCount={activeCount}
|
||||||
|
filter={filter}
|
||||||
|
onClearCompleted={this.handleClearCompleted.bind(this)}
|
||||||
|
onShow={this.handleShow.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
const { filter } = this.state;
|
||||||
|
|
||||||
|
const filteredTodos = todos.filter(TODO_FILTERS[filter]);
|
||||||
|
const completedCount = todos.reduce(
|
||||||
|
(count, todo) => (todo.completed ? count + 1 : count),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="main">
|
||||||
|
{this.renderToggleAll(completedCount)}
|
||||||
|
<ul className="todo-list">
|
||||||
|
{filteredTodos.map((todo) => (
|
||||||
|
<TodoItem key={todo.id} todo={todo} {...actions} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{this.renderFooter(completedCount)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MainSection.propTypes = {
|
||||||
|
todos: PropTypes.array.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MainSection;
|
||||||
76
extension/examples/todomvc/components/TodoItem.js
Normal file
76
extension/examples/todomvc/components/TodoItem.js
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import TodoTextInput from './TodoTextInput';
|
||||||
|
|
||||||
|
class TodoItem extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
editing: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoubleClick() {
|
||||||
|
this.setState({ editing: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave(id, text) {
|
||||||
|
if (text.length === 0) {
|
||||||
|
this.props.deleteTodo(id);
|
||||||
|
} else {
|
||||||
|
this.props.editTodo(id, text);
|
||||||
|
}
|
||||||
|
this.setState({ editing: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { todo, completeTodo, deleteTodo } = this.props;
|
||||||
|
|
||||||
|
let element;
|
||||||
|
if (this.state.editing) {
|
||||||
|
element = (
|
||||||
|
<TodoTextInput
|
||||||
|
text={todo.text}
|
||||||
|
editing={this.state.editing}
|
||||||
|
onSave={(text) => this.handleSave(todo.id, text)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
element = (
|
||||||
|
<div className="view">
|
||||||
|
<input
|
||||||
|
className="toggle"
|
||||||
|
type="checkbox"
|
||||||
|
checked={todo.completed}
|
||||||
|
onChange={() => completeTodo(todo.id)}
|
||||||
|
/>
|
||||||
|
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
|
||||||
|
{todo.text}
|
||||||
|
</label>
|
||||||
|
<button className="destroy" onClick={() => deleteTodo(todo.id)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className={classnames({
|
||||||
|
completed: todo.completed,
|
||||||
|
editing: this.state.editing,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{element}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoItem.propTypes = {
|
||||||
|
todo: PropTypes.object.isRequired,
|
||||||
|
editTodo: PropTypes.func.isRequired,
|
||||||
|
deleteTodo: PropTypes.func.isRequired,
|
||||||
|
completeTodo: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoItem;
|
||||||
60
extension/examples/todomvc/components/TodoTextInput.js
Normal file
60
extension/examples/todomvc/components/TodoTextInput.js
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
class TodoTextInput extends Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
text: this.props.text || '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(e) {
|
||||||
|
const text = e.target.value.trim();
|
||||||
|
if (e.which === 13) {
|
||||||
|
this.props.onSave(text);
|
||||||
|
if (this.props.newTodo) {
|
||||||
|
this.setState({ text: '' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.setState({ text: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur(e) {
|
||||||
|
if (!this.props.newTodo) {
|
||||||
|
this.props.onSave(e.target.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={classnames({
|
||||||
|
edit: this.props.editing,
|
||||||
|
'new-todo': this.props.newTodo,
|
||||||
|
})}
|
||||||
|
type="text"
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
autoFocus={true}
|
||||||
|
value={this.state.text}
|
||||||
|
onBlur={this.handleBlur.bind(this)}
|
||||||
|
onChange={this.handleChange.bind(this)}
|
||||||
|
onKeyDown={this.handleSubmit.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TodoTextInput.propTypes = {
|
||||||
|
onSave: PropTypes.func.isRequired,
|
||||||
|
text: PropTypes.string,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
editing: PropTypes.bool,
|
||||||
|
newTodo: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TodoTextInput;
|
||||||
6
extension/examples/todomvc/constants/ActionTypes.js
Normal file
6
extension/examples/todomvc/constants/ActionTypes.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const ADD_TODO = 'ADD_TODO';
|
||||||
|
export const DELETE_TODO = 'DELETE_TODO';
|
||||||
|
export const EDIT_TODO = 'EDIT_TODO';
|
||||||
|
export const COMPLETE_TODO = 'COMPLETE_TODO';
|
||||||
|
export const COMPLETE_ALL = 'COMPLETE_ALL';
|
||||||
|
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED';
|
||||||
3
extension/examples/todomvc/constants/TodoFilters.js
Normal file
3
extension/examples/todomvc/constants/TodoFilters.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const SHOW_ALL = 'show_all';
|
||||||
|
export const SHOW_COMPLETED = 'show_completed';
|
||||||
|
export const SHOW_ACTIVE = 'show_active';
|
||||||
38
extension/examples/todomvc/containers/App.js
Normal file
38
extension/examples/todomvc/containers/App.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import Header from '../components/Header';
|
||||||
|
import MainSection from '../components/MainSection';
|
||||||
|
import * as TodoActions from '../actions/todos';
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
const { todos, actions } = this.props;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header addTodo={actions.addTodo} />
|
||||||
|
<MainSection todos={todos} actions={actions} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.propTypes = {
|
||||||
|
todos: PropTypes.array.isRequired,
|
||||||
|
actions: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
todos: state.todos,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
actions: bindActionCreators(TodoActions, dispatch),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
10
extension/examples/todomvc/index.html
Normal file
10
extension/examples/todomvc/index.html
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Redux TodoMVC example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="todoapp" id="root"></div>
|
||||||
|
<script src="/static/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
extension/examples/todomvc/index.js
Normal file
16
extension/examples/todomvc/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'babel-polyfill';
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import App from './containers/App';
|
||||||
|
import configureStore from './store/configureStore';
|
||||||
|
import 'todomvc-app-css/index.css';
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App />
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user