mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-25 11:03:57 +03:00
Move redux-devtools-inspector package (#429)
* Move from zalmoxisus/remotedev-inspector-monitor fork * Fix linting * Add credits * Upgrade to react 16 Moved from zalmoxisus/remotedev-inspector-monitor/pull/5 * Upgrade dependences * Add demo for ES6 map From alexkuz/redux-devtools-inspector/commit/9dfaaabcfba7913fd15ee6ee43627e0c eb1d5c7b
This commit is contained in:
parent
4187bc1797
commit
89880265a6
15
packages/redux-devtools-inspector/.babelrc
Normal file
15
packages/redux-devtools-inspector/.babelrc
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-0", "react"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": [["react-transform", {
|
||||
"transforms": [{
|
||||
"transform": "react-transform-hmr",
|
||||
"imports": ["react"],
|
||||
"locals": ["module"]
|
||||
}]
|
||||
}]]
|
||||
}
|
||||
}
|
||||
}
|
57
packages/redux-devtools-inspector/.eslintrc
Normal file
57
packages/redux-devtools-inspector/.eslintrc
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"no-undef": ["error"],
|
||||
"no-trailing-spaces": ["warn"],
|
||||
"space-before-blocks": ["warn", "always"],
|
||||
"no-unused-expressions": ["off"],
|
||||
"no-underscore-dangle": ["off"],
|
||||
"quote-props": ["warn", "as-needed"],
|
||||
"no-multi-spaces": ["off"],
|
||||
"no-unused-vars": ["warn"],
|
||||
"no-loop-func": ["off"],
|
||||
"key-spacing": ["off"],
|
||||
"max-len": ["warn", 100],
|
||||
"strict": ["off"],
|
||||
"eol-last": ["warn"],
|
||||
"no-console": ["warn"],
|
||||
"indent": ["warn", 2],
|
||||
"quotes": ["warn", "single", "avoid-escape"],
|
||||
"curly": ["off"],
|
||||
"jsx-quotes": ["warn", "prefer-single"],
|
||||
|
||||
"react/jsx-boolean-value": "warn",
|
||||
"react/jsx-no-undef": "error",
|
||||
"react/jsx-uses-react": "warn",
|
||||
"react/jsx-uses-vars": "warn",
|
||||
"react/no-did-mount-set-state": "warn",
|
||||
"react/no-did-update-set-state": "warn",
|
||||
"react/no-multi-comp": "off",
|
||||
"react/no-unknown-property": "error",
|
||||
"react/react-in-jsx-scope": "error",
|
||||
"react/self-closing-comp": "warn",
|
||||
"react/jsx-wrap-multilines": "warn",
|
||||
|
||||
"generator-star-spacing": "off",
|
||||
"new-cap": "off",
|
||||
"object-curly-spacing": "off",
|
||||
"object-shorthand": "off",
|
||||
|
||||
"babel/generator-star-spacing": "warn",
|
||||
"babel/new-cap": "warn",
|
||||
"babel/object-curly-spacing": ["warn", "always"],
|
||||
"babel/object-shorthand": "warn"
|
||||
},
|
||||
"plugins": [
|
||||
"react",
|
||||
"babel"
|
||||
],
|
||||
"settings": {
|
||||
"ecmascript": 6,
|
||||
"jsx": true
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
}
|
||||
}
|
8
packages/redux-devtools-inspector/.npmignore
Normal file
8
packages/redux-devtools-inspector/.npmignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
static
|
||||
src
|
||||
demo
|
||||
.*
|
||||
webpack.config.js
|
||||
index.html
|
||||
*.gif
|
||||
*.png
|
21
packages/redux-devtools-inspector/LICENSE
Normal file
21
packages/redux-devtools-inspector/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2015 Alexander Kuznetsov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
68
packages/redux-devtools-inspector/README.md
Normal file
68
packages/redux-devtools-inspector/README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# redux-devtools-inspector
|
||||
|
||||
[![npm version](https://badge.fury.io/js/redux-devtools-inspector.svg)](https://badge.fury.io/js/redux-devtools-inspector)
|
||||
|
||||
A state monitor for [Redux DevTools](https://github.com/reduxjs/redux-devtools) that provides a convenient way to inspect "real world" app states that could be complicated and deeply nested. Created by [@alexkuz](https://github.com/alexkuz) and merged from [`alexkuz/redux-devtools-inspector`](https://github.com/romseguy/map2tree) into [`reduxjs/redux-devtools` monorepo](https://github.com/reduxjs/redux-devtools).
|
||||
|
||||
![](https://raw.githubusercontent.com/alexkuz/redux-devtools-inspector/master/demo.gif)
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
npm install --save-dev redux-devtools-inspector
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
You can use `Inspector` as the only monitor in your app:
|
||||
|
||||
##### `containers/DevTools.js`
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { createDevTools } from 'redux-devtools';
|
||||
import Inspector from 'redux-devtools-inspector';
|
||||
|
||||
export default createDevTools(
|
||||
<Inspector />
|
||||
);
|
||||
```
|
||||
|
||||
Then you can render `<DevTools>` to any place inside app or even into a separate popup window.
|
||||
|
||||
Alternative, you can use it together with [`DockMonitor`](https://github.com/gaearon/redux-devtools-dock-monitor) to make it dockable.
|
||||
Consult the [`DockMonitor` README](https://github.com/gaearon/redux-devtools-dock-monitor) for details of this approach.
|
||||
|
||||
[Read how to start using Redux DevTools.](https://github.com/gaearon/redux-devtools)
|
||||
|
||||
### Features
|
||||
|
||||
The inspector displays a list of actions and a preview panel which shows the state after the selected action and a diff with the previous state. If no actions are selected, the last state is shown.
|
||||
|
||||
You may pin a certain part of the state to only track its changes.
|
||||
|
||||
### Props
|
||||
|
||||
Name | Type | Description
|
||||
------------------ | ---------------- | -------------
|
||||
`theme` | Object or string | Contains either [base16](https://github.com/chriskempson/base16) theme name or object, that can be `base16` colors map or object containing classnames or styles.
|
||||
`invertTheme` | Boolean | Inverts theme color luminance, making light theme out of dark theme and vice versa.
|
||||
`supportImmutable` | Boolean | Better `Immutable` rendering in `Diff` (can affect performance if state has huge objects/arrays). `false` by default.
|
||||
`tabs` | Array or function | Overrides list of tabs (see below)
|
||||
`diffObjectHash` | Function | Optional callback for better array handling in diffs (see [jsondiffpatch docs](https://github.com/benjamine/jsondiffpatch/blob/master/docs/arrays.md))
|
||||
`diffPropertyFilter` | Function | Optional callback for ignoring particular props in diff (see [jsondiffpatch docs](https://github.com/benjamine/jsondiffpatch#options))
|
||||
|
||||
|
||||
If `tabs` is a function, it receives a list of default tabs and should return updated list, for example:
|
||||
```
|
||||
defaultTabs => [...defaultTabs, { name: 'My Tab', component: MyTab }]
|
||||
```
|
||||
If `tabs` is an array, only provided tabs are rendered.
|
||||
|
||||
`component` is provided with `action` and other props, see [`ActionPreview.jsx`](src/ActionPreview.jsx#L42) for reference.
|
||||
|
||||
Usage example: [`redux-devtools-test-generator`](https://github.com/zalmoxisus/redux-devtools-test-generator#containersdevtoolsjs).
|
||||
|
||||
### License
|
||||
|
||||
MIT
|
BIN
packages/redux-devtools-inspector/demo.gif
Normal file
BIN
packages/redux-devtools-inspector/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"import": true
|
||||
}
|
14
packages/redux-devtools-inspector/demo/src/index.html
Normal file
14
packages/redux-devtools-inspector/demo/src/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
<title><%= htmlWebpackPlugin.options.package.name %></title>
|
||||
<meta name="description" content="<%= htmlWebpackPlugin.options.package.description %>">
|
||||
<link href="http://fonts.googleapis.com/css?family=Noto+Sans|Roboto:400,300,500" rel="stylesheet" type="text/css">
|
||||
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.5/paper/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<a href="<%= htmlWebpackPlugin.options.package.repository.url %>"><img style="z-index: 999999999; position: fixed; top: 0; left: 0; border: 0;" src="https://camo.githubusercontent.com/121cd7cbdc3e4855075ea8b558508b91ac463ac2/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f677265656e5f3030373230302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_left_green_007200.png"></a>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
248
packages/redux-devtools-inspector/demo/src/js/DemoApp.jsx
Normal file
248
packages/redux-devtools-inspector/demo/src/js/DemoApp.jsx
Normal file
|
@ -0,0 +1,248 @@
|
|||
import React from 'react';
|
||||
import PageHeader from 'react-bootstrap/lib/PageHeader';
|
||||
import { connect } from 'react-redux';
|
||||
import pkg from '../../../package.json';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
import FormGroup from 'react-bootstrap/lib/FormGroup';
|
||||
import FormControl from 'react-bootstrap/lib/FormControl';
|
||||
import ControlLabel from 'react-bootstrap/lib/ControlLabel';
|
||||
import Form from 'react-bootstrap/lib/Form';
|
||||
import Col from 'react-bootstrap/lib/Col';
|
||||
import InputGroup from 'react-bootstrap/lib/InputGroup';
|
||||
import Combobox from 'react-input-enhancements/lib/Combobox';
|
||||
import * as base16 from 'base16';
|
||||
import * as inspectorThemes from '../../../src/themes';
|
||||
import getOptions from './getOptions';
|
||||
import { push as pushRoute } from 'react-router-redux';
|
||||
|
||||
const styles = {
|
||||
wrapper: {
|
||||
height: '100vh',
|
||||
width: '80%',
|
||||
margin: '0 auto',
|
||||
paddingTop: '1px'
|
||||
},
|
||||
header: {
|
||||
},
|
||||
content: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '50%'
|
||||
},
|
||||
buttons: {
|
||||
display: 'flex',
|
||||
width: '40rem',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
muted: {
|
||||
color: '#CCCCCC'
|
||||
},
|
||||
button: {
|
||||
margin: '0.5rem'
|
||||
},
|
||||
links: {
|
||||
textAlign: 'center'
|
||||
},
|
||||
link: {
|
||||
margin: '0 0.5rem',
|
||||
cursor: 'pointer',
|
||||
display: 'block'
|
||||
},
|
||||
input: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'left',
|
||||
width: '30rem'
|
||||
}
|
||||
};
|
||||
|
||||
const themeOptions = [
|
||||
...Object.keys(inspectorThemes)
|
||||
.map(value => ({ value, label: inspectorThemes[value].scheme })),
|
||||
null,
|
||||
...Object.keys(base16)
|
||||
.map(value => ({ value, label: base16[value].scheme }))
|
||||
.filter(opt => opt.label)
|
||||
];
|
||||
|
||||
const ROOT = process.env.NODE_ENV === 'production' ? '/redux-devtools-inspector/' : '/';
|
||||
|
||||
function buildUrl(options) {
|
||||
return `${ROOT}?` + [
|
||||
options.useExtension ? 'ext' : '',
|
||||
options.supportImmutable ? 'immutable' : '',
|
||||
options.theme ? 'theme=' + options.theme : '',
|
||||
options.dark ? 'dark' : ''
|
||||
].filter(s => s).join('&');
|
||||
}
|
||||
|
||||
class DemoApp extends React.Component {
|
||||
render() {
|
||||
const options = getOptions();
|
||||
|
||||
return (
|
||||
<div style={styles.wrapper}>
|
||||
<PageHeader style={styles.header}>
|
||||
{pkg.name || <span style={styles.muted}>Package Name</span>}
|
||||
</PageHeader>
|
||||
<h5>{pkg.description || <span style={styles.muted}>Package Description</span>}</h5>
|
||||
<div style={styles.links}>
|
||||
<div style={styles.input}>
|
||||
<Form horizontal>
|
||||
<FormGroup>
|
||||
<Col componentClass={ControlLabel} sm={3}>
|
||||
Theme:
|
||||
</Col>
|
||||
<Col sm={9}>
|
||||
<InputGroup>
|
||||
<Combobox options={themeOptions}
|
||||
value={options.theme}
|
||||
onSelect={value => this.setTheme(options, value)}
|
||||
optionFilters={[]}>
|
||||
{props => <FormControl {...props} type='text' />}
|
||||
</Combobox>
|
||||
<InputGroup.Addon>
|
||||
<a onClick={this.toggleTheme}
|
||||
style={styles.link}>
|
||||
{options.dark ? 'Light theme' : 'Dark theme'}
|
||||
</a>
|
||||
</InputGroup.Addon>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.content}>
|
||||
<div style={styles.buttons}>
|
||||
<Button onClick={this.props.increment} style={styles.button}>
|
||||
Increment
|
||||
</Button>
|
||||
<Button onClick={this.props.push} style={styles.button}>
|
||||
Push
|
||||
</Button>
|
||||
<Button onClick={this.props.pop} style={styles.button}>
|
||||
Pop
|
||||
</Button>
|
||||
<Button onClick={this.props.replace} style={styles.button}>
|
||||
Replace
|
||||
</Button>
|
||||
<Button onClick={this.props.changeNested} style={styles.button}>
|
||||
Change Nested
|
||||
</Button>
|
||||
<Button onClick={this.props.pushHugeArray} style={styles.button}>
|
||||
Push Huge Array
|
||||
</Button>
|
||||
<Button onClick={this.props.addHugeObect} style={styles.button}>
|
||||
Add Huge Object
|
||||
</Button>
|
||||
<Button onClick={this.props.addIterator} style={styles.button}>
|
||||
Add Iterator
|
||||
</Button>
|
||||
<Button onClick={this.props.addRecursive} style={styles.button}>
|
||||
Add Recursive
|
||||
</Button>
|
||||
<Button onClick={this.props.addNativeMap} style={styles.button}>
|
||||
Add Native Map
|
||||
</Button>
|
||||
<Button onClick={this.props.addImmutableMap} style={styles.button}>
|
||||
Add Immutable Map
|
||||
</Button>
|
||||
<Button onClick={this.props.changeImmutableNested} style={styles.button}>
|
||||
Change Immutable Nested
|
||||
</Button>
|
||||
<Button onClick={this.props.hugePayload} style={styles.button}>
|
||||
Huge Payload
|
||||
</Button>
|
||||
<Button onClick={this.props.addFunction} style={styles.button}>
|
||||
Add Function
|
||||
</Button>
|
||||
<Button onClick={this.props.addSymbol} style={styles.button}>
|
||||
Add Symbol
|
||||
</Button>
|
||||
<Button onClick={this.toggleTimeoutUpdate} style={styles.button}>
|
||||
Timeout Update {this.props.timeoutUpdateEnabled ? 'On' : 'Off'}
|
||||
</Button>
|
||||
<Button onClick={this.props.shuffleArray} style={styles.button}>
|
||||
Shuffle Array
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={styles.links}>
|
||||
<a onClick={this.toggleExtension}
|
||||
style={styles.link}>
|
||||
{(options.useExtension ? 'Disable' : 'Enable') + ' Chrome Extension'}
|
||||
</a>
|
||||
<a onClick={this.toggleImmutableSupport}
|
||||
style={styles.link}>
|
||||
{(options.supportImmutable ? 'Disable' : 'Enable') + ' Full Immutable Support'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
toggleExtension = () => {
|
||||
const options = getOptions();
|
||||
|
||||
this.props.pushRoute(buildUrl({ ...options, useExtension: !options.useExtension }));
|
||||
};
|
||||
|
||||
toggleImmutableSupport = () => {
|
||||
const options = getOptions();
|
||||
|
||||
this.props.pushRoute(buildUrl({ ...options, supportImmutable: !options.supportImmutable }));
|
||||
};
|
||||
|
||||
toggleTheme = () => {
|
||||
const options = getOptions();
|
||||
|
||||
this.props.pushRoute(buildUrl({ ...options, dark: !options.dark }));
|
||||
};
|
||||
|
||||
setTheme = (options, theme) => {
|
||||
this.props.pushRoute(buildUrl({ ...options, theme }));
|
||||
};
|
||||
|
||||
toggleTimeoutUpdate = () => {
|
||||
const enabled = !this.props.timeoutUpdateEnabled;
|
||||
this.props.toggleTimeoutUpdate(enabled);
|
||||
|
||||
if (enabled) {
|
||||
this.timeout = setInterval(this.props.timeoutUpdate, 1000);
|
||||
} else {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => state,
|
||||
{
|
||||
toggleTimeoutUpdate: timeoutUpdateEnabled => ({
|
||||
type: 'TOGGLE_TIMEOUT_UPDATE', timeoutUpdateEnabled
|
||||
}),
|
||||
timeoutUpdate: () => ({ type: 'TIMEOUT_UPDATE' }),
|
||||
increment: () => ({ type: 'INCREMENT' }),
|
||||
push: () => ({ type: 'PUSH' }),
|
||||
pop: () => ({ type: 'POP' }),
|
||||
replace: () => ({ type: 'REPLACE' }),
|
||||
changeNested: () => ({ type: 'CHANGE_NESTED' }),
|
||||
pushHugeArray: () => ({ type: 'PUSH_HUGE_ARRAY' }),
|
||||
addIterator: () => ({ type: 'ADD_ITERATOR' }),
|
||||
addHugeObect: () => ({ type: 'ADD_HUGE_OBJECT' }),
|
||||
addRecursive: () => ({ type: 'ADD_RECURSIVE' }),
|
||||
addNativeMap: () => ({ type: 'ADD_NATIVE_MAP' }),
|
||||
addImmutableMap: () => ({ type: 'ADD_IMMUTABLE_MAP' }),
|
||||
changeImmutableNested: () => ({ type: 'CHANGE_IMMUTABLE_NESTED' }),
|
||||
hugePayload: () => ({
|
||||
type: 'HUGE_PAYLOAD',
|
||||
payload: Array.from({ length: 10000 }).map((_, i) => i)
|
||||
}),
|
||||
addFunction: () => ({ type: 'ADD_FUNCTION' }),
|
||||
addSymbol: () => ({ type: 'ADD_SYMBOL' }),
|
||||
shuffleArray: () => ({ type: 'SHUFFLE_ARRAY' }),
|
||||
pushRoute
|
||||
}
|
||||
)(DemoApp);
|
11
packages/redux-devtools-inspector/demo/src/js/getOptions.js
Normal file
11
packages/redux-devtools-inspector/demo/src/js/getOptions.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export default function getOptions() {
|
||||
return {
|
||||
useExtension: window.location.search.indexOf('ext') !== -1,
|
||||
supportImmutable: window.location.search.indexOf('immutable') !== -1,
|
||||
theme: do {
|
||||
const match = window.location.search.match(/theme=([^&]+)/);
|
||||
match ? match[1] : 'inspector'
|
||||
},
|
||||
dark: window.location.search.indexOf('dark') !== -1
|
||||
};
|
||||
}
|
100
packages/redux-devtools-inspector/demo/src/js/index.js
Normal file
100
packages/redux-devtools-inspector/demo/src/js/index.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
import 'babel-polyfill';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import DemoApp from './DemoApp';
|
||||
import { Provider } from 'react-redux';
|
||||
import reducers from './reducers';
|
||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||
import createLogger from 'redux-logger';
|
||||
import { Router, Route, browserHistory } from 'react-router';
|
||||
import { syncHistoryWithStore, routerReducer, routerMiddleware } from 'react-router-redux';
|
||||
import { createDevTools, persistState } from 'redux-devtools';
|
||||
import DevtoolsInspector from '../../../src/DevtoolsInspector';
|
||||
import DockMonitor from 'redux-devtools-dock-monitor';
|
||||
import getOptions from './getOptions';
|
||||
|
||||
function getDebugSessionKey() {
|
||||
const matches = window.location.href.match(/[?&]debug_session=([^&#]+)\b/);
|
||||
return (matches && matches.length > 0)? matches[1] : null;
|
||||
}
|
||||
|
||||
const CustomComponent = () =>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: '20rem'
|
||||
}}>
|
||||
<div>Custom Tab Content</div>
|
||||
</div>;
|
||||
|
||||
const getDevTools = options =>
|
||||
createDevTools(
|
||||
<DockMonitor defaultIsVisible
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
changePositionKey='ctrl-q'
|
||||
changeMonitorKey='ctrl-m'>
|
||||
<DevtoolsInspector theme={options.theme}
|
||||
shouldPersistState
|
||||
invertTheme={!options.dark}
|
||||
supportImmutable={options.supportImmutable}
|
||||
tabs={defaultTabs => [{
|
||||
name: 'Custom Tab',
|
||||
component: CustomComponent
|
||||
}, ...defaultTabs]} />
|
||||
</DockMonitor>
|
||||
);
|
||||
|
||||
const ROOT = process.env.NODE_ENV === 'production' ? '/redux-devtools-inspector/' : '/';
|
||||
|
||||
let DevTools = getDevTools(getOptions());
|
||||
|
||||
const reduxRouterMiddleware = routerMiddleware(browserHistory);
|
||||
|
||||
const enhancer = compose(
|
||||
applyMiddleware(createLogger(), reduxRouterMiddleware),
|
||||
(...args) => {
|
||||
const useDevtoolsExtension = !!window.__REDUX_DEVTOOLS_EXTENSION__ && getOptions().useExtension;
|
||||
const instrument = useDevtoolsExtension ?
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__() : DevTools.instrument();
|
||||
return instrument(...args);
|
||||
},
|
||||
persistState(getDebugSessionKey())
|
||||
);
|
||||
|
||||
const store = createStore(combineReducers({
|
||||
...reducers,
|
||||
routing: routerReducer
|
||||
}), {}, enhancer);
|
||||
|
||||
const history = syncHistoryWithStore(browserHistory, store);
|
||||
|
||||
const handleRouterUpdate = () => {
|
||||
renderApp(getOptions());
|
||||
};
|
||||
|
||||
const router = (
|
||||
<Router history={history} onUpdate={handleRouterUpdate}>
|
||||
<Route path={ROOT}
|
||||
component={DemoApp} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const renderApp = options => {
|
||||
DevTools = getDevTools(options);
|
||||
const useDevtoolsExtension = !!window.__REDUX_DEVTOOLS_EXTENSION__ && options.useExtension;
|
||||
|
||||
return render(
|
||||
<Provider store={store}>
|
||||
<div>
|
||||
{router}
|
||||
{!useDevtoolsExtension && <DevTools />}
|
||||
</div>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
}
|
||||
|
||||
renderApp(getOptions());
|
123
packages/redux-devtools-inspector/demo/src/js/reducers.js
Normal file
123
packages/redux-devtools-inspector/demo/src/js/reducers.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
import Immutable from 'immutable';
|
||||
import shuffle from 'lodash.shuffle';
|
||||
|
||||
const NESTED = {
|
||||
long: {
|
||||
nested: [{
|
||||
path: {
|
||||
to: {
|
||||
a: 'key'
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const IMMUTABLE_NESTED = Immutable.fromJS(NESTED);
|
||||
|
||||
/* eslint-disable babel/new-cap */
|
||||
|
||||
const IMMUTABLE_MAP = Immutable.Map({
|
||||
map: Immutable.Map({ a:1, b: 2, c: 3 }),
|
||||
list: Immutable.List(['a', 'b', 'c']),
|
||||
set: Immutable.Set(['a', 'b', 'c']),
|
||||
stack: Immutable.Stack(['a', 'b', 'c']),
|
||||
seq: Immutable.Seq.of(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
});
|
||||
|
||||
const NATIVE_MAP = new window.Map([
|
||||
['map', new window.Map([
|
||||
[{ first: true }, 1],
|
||||
['second', 2]
|
||||
])],
|
||||
['weakMap', new window.WeakMap([
|
||||
[{ first: true }, 1],
|
||||
[{ second: 1 }, 2]
|
||||
])],
|
||||
['set', new window.Set([
|
||||
{ first: true },
|
||||
'second'
|
||||
])],
|
||||
['weakSet', new window.WeakSet([
|
||||
{ first: true },
|
||||
{ second: 1 }
|
||||
])]
|
||||
]);
|
||||
|
||||
/* eslint-enable babel/new-cap */
|
||||
|
||||
const HUGE_ARRAY = Array.from({ length: 5000 })
|
||||
.map((_, key) => ({ str: 'key ' + key }));
|
||||
|
||||
const HUGE_OBJECT = Array.from({ length: 5000 })
|
||||
.reduce((o, _, key) => (o['key ' + key] = 'item ' + key, o), {});
|
||||
|
||||
const FUNC = function (a, b, c) { return a + b + c; };
|
||||
|
||||
const RECURSIVE = {};
|
||||
RECURSIVE.obj = RECURSIVE;
|
||||
|
||||
function createIterator() {
|
||||
const iterable = {};
|
||||
iterable[window.Symbol.iterator] = function *iterator() {
|
||||
for (var i = 0; i < 333; i++) {
|
||||
yield 'item ' + i;
|
||||
}
|
||||
}
|
||||
|
||||
return iterable;
|
||||
}
|
||||
|
||||
const DEFAULT_SHUFFLE_ARRAY = [0, 1, null, { id: 1 }, { id: 2 }, 'string'];
|
||||
|
||||
export default {
|
||||
timeoutUpdateEnabled: (state=false, action) => action.type === 'TOGGLE_TIMEOUT_UPDATE' ?
|
||||
action.timeoutUpdateEnabled : state,
|
||||
store: (state=0, action) => action.type === 'INCREMENT' ? state + 1 : state,
|
||||
undefined: (state={ val: undefined }) => state,
|
||||
null: (state=null) => state,
|
||||
func: (state=() => {}) => state,
|
||||
array: (state=[], action) => action.type === 'PUSH' ?
|
||||
[...state, Math.random()] : (
|
||||
action.type === 'POP' ? state.slice(0, state.length - 1) : (
|
||||
action.type === 'REPLACE' ? [Math.random(), ...state.slice(1)] : state
|
||||
)
|
||||
),
|
||||
hugeArrays: (state=[], action) => action.type === 'PUSH_HUGE_ARRAY' ?
|
||||
[ ...state, ...HUGE_ARRAY ] : state,
|
||||
hugeObjects: (state=[], action) => action.type === 'ADD_HUGE_OBJECT' ?
|
||||
[ ...state, HUGE_OBJECT ] : state,
|
||||
iterators: (state=[], action) => action.type === 'ADD_ITERATOR' ?
|
||||
[...state, createIterator()] : state,
|
||||
nested: (state=NESTED, action) =>
|
||||
action.type === 'CHANGE_NESTED' ? {
|
||||
...state,
|
||||
long: {
|
||||
nested: [{
|
||||
path: {
|
||||
to: {
|
||||
a: state.long.nested[0].path.to.a + '!'
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
} : state,
|
||||
recursive: (state=[], action) => action.type === 'ADD_RECURSIVE' ?
|
||||
[...state, { ...RECURSIVE }] : state,
|
||||
immutables: (state=[], action) => action.type === 'ADD_IMMUTABLE_MAP' ?
|
||||
[...state, IMMUTABLE_MAP] : state,
|
||||
maps: (state=[], action) => action.type === 'ADD_NATIVE_MAP' ?
|
||||
[...state, NATIVE_MAP] : state,
|
||||
immutableNested: (state=IMMUTABLE_NESTED, action) => action.type === 'CHANGE_IMMUTABLE_NESTED' ?
|
||||
state.updateIn(
|
||||
['long', 'nested', 0, 'path', 'to', 'a'],
|
||||
str => str + '!'
|
||||
) : state,
|
||||
addFunction: (state=null, action) => action.type === 'ADD_FUNCTION' ?
|
||||
{ f: FUNC } : state,
|
||||
addSymbol: (state=null, action) => action.type === 'ADD_SYMBOL' ?
|
||||
{ s: window.Symbol('symbol'), error: new Error('TEST') } : state,
|
||||
shuffleArray: (state=DEFAULT_SHUFFLE_ARRAY, action) =>
|
||||
action.type === 'SHUFFLE_ARRAY' ?
|
||||
shuffle(state) : state
|
||||
};
|
92
packages/redux-devtools-inspector/package.json
Normal file
92
packages/redux-devtools-inspector/package.json
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"name": "redux-devtools-inspector",
|
||||
"version": "0.11.0",
|
||||
"description": "Redux DevTools Diff Monitor",
|
||||
"scripts": {
|
||||
"build:lib": "NODE_ENV=production babel src --out-dir lib",
|
||||
"build:demo": "NODE_ENV=production webpack -p",
|
||||
"stats": "webpack --profile --json > stats.json",
|
||||
"start": "webpack-dev-server",
|
||||
"lint": "eslint --ext .jsx,.js --max-warnings 0 src",
|
||||
"preversion": "npm run lint",
|
||||
"version": "npm run build:demo && git add -A .",
|
||||
"postversion": "git push",
|
||||
"prepublish": "npm run build:lib",
|
||||
"gh": "git subtree push --prefix demo/dist origin gh-pages"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
"url": "https://github.com/reduxjs/redux-devtools"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^6.3.26",
|
||||
"babel-cli": "^6.4.5",
|
||||
"babel-core": "^6.4.5",
|
||||
"babel-eslint": "^7.1.0",
|
||||
"babel-loader": "^6.2.2",
|
||||
"babel-plugin-react-transform": "^2.0.0",
|
||||
"babel-plugin-transform-runtime": "^6.4.3",
|
||||
"babel-preset-es2015": "^6.3.13",
|
||||
"babel-preset-react": "^6.3.13",
|
||||
"babel-preset-stage-0": "^6.3.13",
|
||||
"base16": "^1.0.0",
|
||||
"chokidar": "^1.6.1",
|
||||
"clean-webpack-plugin": "^0.1.8",
|
||||
"eslint": "^4.0.0",
|
||||
"eslint-loader": "^1.2.1",
|
||||
"eslint-plugin-babel": "^4.0.0",
|
||||
"eslint-plugin-react": "^6.6.0",
|
||||
"export-files-webpack-plugin": "0.0.1",
|
||||
"html-webpack-plugin": "^2.8.1",
|
||||
"imports-loader": "^0.6.5",
|
||||
"json-loader": "^0.5.4",
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"nyan-progress-webpack-plugin": "^1.1.4",
|
||||
"pre-commit": "^1.1.3",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.4.2",
|
||||
"react-bootstrap": "^0.30.6",
|
||||
"react-dom": "^16.4.2",
|
||||
"react-input-enhancements": "^0.7.5",
|
||||
"react-redux": "^6.0.0",
|
||||
"react-router": "^3.0.0",
|
||||
"react-router-redux": "^4.0.2",
|
||||
"react-transform-hmr": "^1.0.2",
|
||||
"redux": "^4.0.0",
|
||||
"redux-devtools": "^3.1.0",
|
||||
"redux-devtools-dock-monitor": "^1.0.1",
|
||||
"redux-logger": "^2.5.2",
|
||||
"webpack": "^1.12.13",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0",
|
||||
"react-dom": ">=15.0.0"
|
||||
},
|
||||
"author": "Alexander <alexkuz@gmail.com> (http://kuzya.org/)",
|
||||
"contributors": [
|
||||
"Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.3.19",
|
||||
"dateformat": "^1.0.12",
|
||||
"hex-rgba": "^1.0.0",
|
||||
"immutable": "^3.7.6",
|
||||
"javascript-stringify": "^1.1.0",
|
||||
"jsondiffpatch": "^0.2.4",
|
||||
"jss": "^6.0.0",
|
||||
"jss-nested": "^3.0.0",
|
||||
"jss-vendor-prefixer": "^4.0.0",
|
||||
"lodash.debounce": "^4.0.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-base16-styling": "^0.4.1",
|
||||
"react-dragula": "^1.1.17",
|
||||
"react-json-tree": "^0.11.1",
|
||||
"react-pure-render": "^1.0.2",
|
||||
"redux-devtools-themes": "^1.0.0"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint"
|
||||
]
|
||||
}
|
3
packages/redux-devtools-inspector/src/.noderequirer.json
Normal file
3
packages/redux-devtools-inspector/src/.noderequirer.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"import": true
|
||||
}
|
124
packages/redux-devtools-inspector/src/ActionList.jsx
Normal file
124
packages/redux-devtools-inspector/src/ActionList.jsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import dragula from 'react-dragula';
|
||||
import ActionListRow from './ActionListRow';
|
||||
import ActionListHeader from './ActionListHeader';
|
||||
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||
|
||||
function getTimestamps(actions, actionIds, actionId) {
|
||||
const idx = actionIds.indexOf(actionId);
|
||||
const prevActionId = actionIds[idx - 1];
|
||||
|
||||
return {
|
||||
current: actions[actionId].timestamp,
|
||||
previous: idx ? actions[prevActionId].timestamp : 0
|
||||
};
|
||||
}
|
||||
|
||||
export default class ActionList extends Component {
|
||||
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const node = this.node;
|
||||
if (!node) {
|
||||
this.scrollDown = true;
|
||||
} else if (this.props.lastActionId !== nextProps.lastActionId) {
|
||||
const { scrollTop, offsetHeight, scrollHeight } = node;
|
||||
this.scrollDown = Math.abs(scrollHeight - (scrollTop + offsetHeight)) < 50;
|
||||
} else {
|
||||
this.scrollDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.scrollDown = true;
|
||||
this.scrollToBottom();
|
||||
|
||||
if (!this.props.draggableActions) return;
|
||||
const container = ReactDOM.findDOMNode(this.refs.rows);
|
||||
this.drake = dragula([container], {
|
||||
copy: false,
|
||||
copySortSource: false,
|
||||
mirrorContainer: container,
|
||||
accepts: (el, target, source, sibling) => (
|
||||
!sibling || parseInt(sibling.getAttribute('data-id'))
|
||||
),
|
||||
moves: (el, source, handle) => (
|
||||
parseInt(el.getAttribute('data-id')) &&
|
||||
handle.className.indexOf('selectorButton') !== 0
|
||||
),
|
||||
}).on('drop', (el, target, source, sibling) => {
|
||||
let beforeActionId = this.props.actionIds.length;
|
||||
if (sibling && sibling.className.indexOf('gu-mirror') === -1) {
|
||||
beforeActionId = parseInt(sibling.getAttribute('data-id'));
|
||||
}
|
||||
const actionId = parseInt(el.getAttribute('data-id'));
|
||||
this.props.onReorderAction(actionId, beforeActionId)
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.drake) this.drake.destroy();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
if (this.scrollDown && this.node) {
|
||||
this.node.scrollTop = this.node.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
getRef = node => {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { styling, actions, actionIds, isWideLayout, onToggleAction, skippedActionIds,
|
||||
selectedActionId, startActionId, onSelect, onSearch, searchValue, currentActionId,
|
||||
hideMainButtons, hideActionButtons, onCommit, onSweep, onJumpToState } = this.props;
|
||||
const lowerSearchValue = searchValue && searchValue.toLowerCase();
|
||||
const filteredActionIds = searchValue ? actionIds.filter(
|
||||
id => actions[id].action.type.toLowerCase().indexOf(lowerSearchValue) !== -1
|
||||
) : actionIds;
|
||||
|
||||
return (
|
||||
<div key='actionList'
|
||||
{...styling(['actionList', isWideLayout && 'actionListWide'], isWideLayout)}>
|
||||
<ActionListHeader styling={styling}
|
||||
onSearch={onSearch}
|
||||
onCommit={onCommit}
|
||||
onSweep={onSweep}
|
||||
hideMainButtons={hideMainButtons}
|
||||
hasSkippedActions={skippedActionIds.length > 0}
|
||||
hasStagedActions={actionIds.length > 1} />
|
||||
<div {...styling('actionListRows')} ref={this.getRef}>
|
||||
{filteredActionIds.map(actionId =>
|
||||
(<ActionListRow key={actionId}
|
||||
styling={styling}
|
||||
actionId={actionId}
|
||||
isInitAction={!actionId}
|
||||
isSelected={
|
||||
startActionId !== null &&
|
||||
actionId >= startActionId && actionId <= selectedActionId ||
|
||||
actionId === selectedActionId
|
||||
}
|
||||
isInFuture={
|
||||
actionIds.indexOf(actionId) > actionIds.indexOf(currentActionId)
|
||||
}
|
||||
onSelect={(e) => onSelect(e, actionId)}
|
||||
timestamps={getTimestamps(actions, actionIds, actionId)}
|
||||
action={actions[actionId].action}
|
||||
onToggleClick={() => onToggleAction(actionId)}
|
||||
onJumpClick={() => onJumpToState(actionId)}
|
||||
onCommitClick={() => onCommit(actionId)}
|
||||
hideActionButtons={hideActionButtons}
|
||||
isSkipped={skippedActionIds.indexOf(actionId) !== -1} />)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
43
packages/redux-devtools-inspector/src/ActionListHeader.jsx
Normal file
43
packages/redux-devtools-inspector/src/ActionListHeader.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import RightSlider from './RightSlider';
|
||||
|
||||
const getActiveButtons = (hasSkippedActions) => [
|
||||
hasSkippedActions && 'Sweep',
|
||||
'Commit'
|
||||
].filter(a => a);
|
||||
|
||||
const ActionListHeader =
|
||||
({
|
||||
styling, onSearch, hasSkippedActions, hasStagedActions, onCommit, onSweep, hideMainButtons
|
||||
}) =>
|
||||
(<div {...styling('actionListHeader')}>
|
||||
<input
|
||||
{...styling('actionListHeaderSearch')}
|
||||
onChange={e => onSearch(e.target.value)}
|
||||
placeholder='filter...'
|
||||
/>
|
||||
{!hideMainButtons &&
|
||||
<div {...styling('actionListHeaderWrapper')}>
|
||||
<RightSlider shown={hasStagedActions} styling={styling}>
|
||||
<div {...styling('actionListHeaderSelector')}>
|
||||
{getActiveButtons(hasSkippedActions).map(btn =>
|
||||
(<div
|
||||
key={btn}
|
||||
onClick={() => ({
|
||||
Commit: onCommit,
|
||||
Sweep: onSweep
|
||||
})[btn]()}
|
||||
{...styling([
|
||||
'selectorButton',
|
||||
'selectorButtonSmall'], false, true)}
|
||||
>
|
||||
{btn}
|
||||
</div>)
|
||||
)}
|
||||
</div>
|
||||
</RightSlider>
|
||||
</div>
|
||||
}
|
||||
</div>);
|
||||
|
||||
export default ActionListHeader;
|
129
packages/redux-devtools-inspector/src/ActionListRow.jsx
Normal file
129
packages/redux-devtools-inspector/src/ActionListRow.jsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import React, { Component } from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||
import dateformat from 'dateformat';
|
||||
import debounce from 'lodash.debounce';
|
||||
import RightSlider from './RightSlider';
|
||||
|
||||
const BUTTON_SKIP = 'Skip';
|
||||
const BUTTON_JUMP = 'Jump';
|
||||
|
||||
export default class ActionListRow extends Component {
|
||||
state = { hover: false };
|
||||
|
||||
static propTypes = {
|
||||
styling: PropTypes.func.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
action: PropTypes.object.isRequired,
|
||||
isInFuture: PropTypes.bool.isRequired,
|
||||
isInitAction: PropTypes.bool.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
timestamps: PropTypes.shape({
|
||||
current: PropTypes.number.isRequired,
|
||||
previous: PropTypes.number.isRequired
|
||||
}).isRequired,
|
||||
isSkipped: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
shouldComponentUpdate = shouldPureComponentUpdate
|
||||
|
||||
render() {
|
||||
const { styling, isSelected, action, actionId, isInitAction, onSelect,
|
||||
timestamps, isSkipped, isInFuture, hideActionButtons } = this.props;
|
||||
const { hover } = this.state;
|
||||
const timeDelta = timestamps.current - timestamps.previous;
|
||||
const showButtons = hover && !isInitAction || isSkipped;
|
||||
|
||||
const isButtonSelected = btn =>
|
||||
btn === BUTTON_SKIP && isSkipped;
|
||||
|
||||
let actionType = action.type;
|
||||
if (typeof actionType === 'undefined') actionType = '<UNDEFINED>';
|
||||
else if (actionType === null) actionType = '<NULL>';
|
||||
else actionType = actionType.toString() || '<EMPTY>';
|
||||
|
||||
return (
|
||||
<div onClick={onSelect}
|
||||
onMouseEnter={!hideActionButtons && this.handleMouseEnter}
|
||||
onMouseLeave={!hideActionButtons && this.handleMouseLeave}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseEnter}
|
||||
data-id={actionId}
|
||||
{...styling([
|
||||
'actionListItem',
|
||||
isSelected && 'actionListItemSelected',
|
||||
isSkipped && 'actionListItemSkipped',
|
||||
isInFuture && 'actionListFromFuture'
|
||||
], isSelected, action)}>
|
||||
<div {...styling(['actionListItemName', isSkipped && 'actionListItemNameSkipped'])}>
|
||||
{actionType}
|
||||
</div>
|
||||
{hideActionButtons ?
|
||||
<RightSlider styling={styling} shown>
|
||||
<div {...styling('actionListItemTime')}>
|
||||
{timeDelta === 0 ? '+00:00:00' :
|
||||
dateformat(timeDelta, timestamps.previous ? '+MM:ss.L' : 'h:MM:ss.L')}
|
||||
</div>
|
||||
</RightSlider>
|
||||
:
|
||||
<div {...styling('actionListItemButtons')}>
|
||||
<RightSlider styling={styling} shown={!showButtons} rotate>
|
||||
<div {...styling('actionListItemTime')}>
|
||||
{timeDelta === 0 ? '+00:00:00' :
|
||||
dateformat(timeDelta, timestamps.previous ? '+MM:ss.L' : 'h:MM:ss.L')}
|
||||
</div>
|
||||
</RightSlider>
|
||||
<RightSlider styling={styling} shown={showButtons} rotate>
|
||||
<div {...styling('actionListItemSelector')}>
|
||||
{[BUTTON_JUMP, BUTTON_SKIP].map(btn => (!isInitAction || btn !== BUTTON_SKIP) &&
|
||||
<div key={btn}
|
||||
onClick={this.handleButtonClick.bind(this, btn)}
|
||||
{...styling([
|
||||
'selectorButton',
|
||||
isButtonSelected(btn) && 'selectorButtonSelected',
|
||||
'selectorButtonSmall'], isButtonSelected(btn), true)}>
|
||||
{btn}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</RightSlider>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleButtonClick(btn, e) {
|
||||
e.stopPropagation();
|
||||
|
||||
switch(btn) {
|
||||
case BUTTON_SKIP:
|
||||
this.props.onToggleClick();
|
||||
break;
|
||||
case BUTTON_JUMP:
|
||||
this.props.onJumpClick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseEnter = e => {
|
||||
if (this.hover) return;
|
||||
this.handleMouseLeave.cancel();
|
||||
this.handleMouseEnterDebounced(e.buttons);
|
||||
}
|
||||
|
||||
handleMouseEnterDebounced = debounce((buttons) => {
|
||||
if (buttons) return;
|
||||
this.setState({ hover: true });
|
||||
}, 150)
|
||||
|
||||
handleMouseLeave = debounce(() => {
|
||||
this.handleMouseEnterDebounced.cancel();
|
||||
if (this.state.hover) this.setState({ hover: false });
|
||||
}, 100)
|
||||
|
||||
handleMouseDown = e => {
|
||||
if (e.target.className.indexOf('selectorButton') === 0) return;
|
||||
this.handleMouseLeave();
|
||||
}
|
||||
}
|
97
packages/redux-devtools-inspector/src/ActionPreview.jsx
Normal file
97
packages/redux-devtools-inspector/src/ActionPreview.jsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import React, { Component } from 'react';
|
||||
import { DEFAULT_STATE } from './redux';
|
||||
import ActionPreviewHeader from './ActionPreviewHeader';
|
||||
import DiffTab from './tabs/DiffTab';
|
||||
import StateTab from './tabs/StateTab';
|
||||
import ActionTab from './tabs/ActionTab';
|
||||
|
||||
const DEFAULT_TABS = [{
|
||||
name: 'Action',
|
||||
component: ActionTab
|
||||
}, {
|
||||
name: 'Diff',
|
||||
component: DiffTab
|
||||
}, {
|
||||
name: 'State',
|
||||
component: StateTab
|
||||
}]
|
||||
|
||||
class ActionPreview extends Component {
|
||||
static defaultProps = {
|
||||
tabName: DEFAULT_STATE.tabName
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
styling, delta, error, nextState, onInspectPath, inspectedPath, tabName,
|
||||
isWideLayout, onSelectTab, action, actions, selectedActionId, startActionId,
|
||||
computedStates, base16Theme, invertTheme, tabs, dataTypeKey, monitorState, updateMonitorState
|
||||
} = this.props;
|
||||
|
||||
const renderedTabs = (typeof tabs === 'function') ?
|
||||
tabs(DEFAULT_TABS) :
|
||||
(tabs ? tabs : DEFAULT_TABS);
|
||||
|
||||
const { component: TabComponent } = (
|
||||
renderedTabs.find(tab => tab.name === tabName)
|
||||
|| renderedTabs.find(tab => tab.name === DEFAULT_STATE.tabName)
|
||||
);
|
||||
|
||||
return (
|
||||
<div key='actionPreview' {...styling('actionPreview')}>
|
||||
<ActionPreviewHeader
|
||||
tabs={renderedTabs}
|
||||
{...{ styling, inspectedPath, onInspectPath, tabName, onSelectTab }}
|
||||
/>
|
||||
{!error &&
|
||||
<div key='actionPreviewContent' {...styling('actionPreviewContent')}>
|
||||
<TabComponent
|
||||
labelRenderer={this.labelRenderer}
|
||||
{...{
|
||||
styling,
|
||||
computedStates,
|
||||
actions,
|
||||
selectedActionId,
|
||||
startActionId,
|
||||
base16Theme,
|
||||
invertTheme,
|
||||
isWideLayout,
|
||||
dataTypeKey,
|
||||
delta,
|
||||
action,
|
||||
nextState,
|
||||
monitorState,
|
||||
updateMonitorState
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{error &&
|
||||
<div {...styling('stateError')}>{error}</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
labelRenderer = ([key, ...rest], nodeType, expanded) => {
|
||||
const { styling, onInspectPath, inspectedPath } = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span {...styling('treeItemKey')}>
|
||||
{key}
|
||||
</span>
|
||||
<span {...styling('treeItemPin')}
|
||||
onClick={() => onInspectPath([
|
||||
...inspectedPath.slice(0, inspectedPath.length - 1),
|
||||
...[key, ...rest].reverse()
|
||||
])}>
|
||||
{'(pin)'}
|
||||
</span>
|
||||
{!expanded && ': '}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ActionPreview;
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react';
|
||||
|
||||
const ActionPreviewHeader =
|
||||
({ styling, inspectedPath, onInspectPath, tabName, onSelectTab, tabs }) =>
|
||||
(<div key='previewHeader' {...styling('previewHeader')}>
|
||||
<div {...styling('tabSelector')}>
|
||||
{tabs.map(tab =>
|
||||
(<div onClick={() => onSelectTab(tab.name)}
|
||||
key={tab.name}
|
||||
{...styling([
|
||||
'selectorButton',
|
||||
tab.name === tabName && 'selectorButtonSelected'
|
||||
], tab.name === tabName)}>
|
||||
{tab.name}
|
||||
</div>)
|
||||
)}
|
||||
</div>
|
||||
<div {...styling('inspectedPath')}>
|
||||
{inspectedPath.length ?
|
||||
<span {...styling('inspectedPathKey')}>
|
||||
<a onClick={() => onInspectPath([])}
|
||||
{...styling('inspectedPathKeyLink')}>
|
||||
{tabName}
|
||||
</a>
|
||||
</span> : tabName
|
||||
}
|
||||
{inspectedPath.map((key, idx) =>
|
||||
idx === inspectedPath.length - 1 ? <span key={key}>{key}</span> :
|
||||
<span key={key}
|
||||
{...styling('inspectedPathKey')}>
|
||||
<a onClick={() => onInspectPath(inspectedPath.slice(0, idx + 1))}
|
||||
{...styling('inspectedPathKeyLink')}>
|
||||
{key}
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
export default ActionPreviewHeader;
|
270
packages/redux-devtools-inspector/src/DevtoolsInspector.js
Normal file
270
packages/redux-devtools-inspector/src/DevtoolsInspector.js
Normal file
|
@ -0,0 +1,270 @@
|
|||
import React, { Component } from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import { createStylingFromTheme, base16Themes } from './utils/createStylingFromTheme';
|
||||
import shouldPureComponentUpdate from 'react-pure-render/function';
|
||||
import ActionList from './ActionList';
|
||||
import ActionPreview from './ActionPreview';
|
||||
import getInspectedState from './utils/getInspectedState';
|
||||
import createDiffPatcher from './createDiffPatcher';
|
||||
import { getBase16Theme } from 'react-base16-styling';
|
||||
import { reducer, updateMonitorState } from './redux';
|
||||
import { ActionCreators } from 'redux-devtools';
|
||||
|
||||
const { commit, sweep, toggleAction, jumpToAction, jumpToState, reorderAction } = ActionCreators;
|
||||
|
||||
function getLastActionId(props) {
|
||||
return props.stagedActionIds[props.stagedActionIds.length - 1];
|
||||
}
|
||||
|
||||
function getCurrentActionId(props, monitorState) {
|
||||
return monitorState.selectedActionId === null ?
|
||||
props.stagedActionIds[props.currentStateIndex] : monitorState.selectedActionId;
|
||||
}
|
||||
|
||||
function getFromState(actionIndex, stagedActionIds, computedStates, monitorState) {
|
||||
const { startActionId } = monitorState;
|
||||
if (startActionId === null) {
|
||||
return actionIndex > 0 ? computedStates[actionIndex - 1] : null;
|
||||
}
|
||||
let fromStateIdx = stagedActionIds.indexOf(startActionId - 1);
|
||||
if (fromStateIdx === -1) fromStateIdx = 0;
|
||||
return computedStates[fromStateIdx];
|
||||
}
|
||||
|
||||
function createIntermediateState(props, monitorState) {
|
||||
const { supportImmutable, computedStates, stagedActionIds,
|
||||
actionsById: actions, diffObjectHash, diffPropertyFilter } = props;
|
||||
const { inspectedStatePath, inspectedActionPath } = monitorState;
|
||||
const currentActionId = getCurrentActionId(props, monitorState);
|
||||
const currentAction = actions[currentActionId] && actions[currentActionId].action;
|
||||
|
||||
const actionIndex = stagedActionIds.indexOf(currentActionId);
|
||||
const fromState = getFromState(actionIndex, stagedActionIds, computedStates, monitorState);
|
||||
const toState = computedStates[actionIndex];
|
||||
const error = toState && toState.error;
|
||||
|
||||
const fromInspectedState = !error && fromState &&
|
||||
getInspectedState(fromState.state, inspectedStatePath, supportImmutable);
|
||||
const toInspectedState =
|
||||
!error && toState && getInspectedState(toState.state, inspectedStatePath, supportImmutable);
|
||||
const delta = !error && fromState && toState &&
|
||||
createDiffPatcher(diffObjectHash, diffPropertyFilter).diff(
|
||||
fromInspectedState,
|
||||
toInspectedState
|
||||
);
|
||||
|
||||
return {
|
||||
delta,
|
||||
nextState: toState && getInspectedState(toState.state, inspectedStatePath, false),
|
||||
action: getInspectedState(currentAction, inspectedActionPath, false),
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
function createThemeState(props) {
|
||||
const base16Theme = getBase16Theme(props.theme, base16Themes);
|
||||
const styling = createStylingFromTheme(props.theme, props.invertTheme);
|
||||
|
||||
return { base16Theme, styling };
|
||||
}
|
||||
|
||||
export default class DevtoolsInspector extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
...createIntermediateState(props, props.monitorState),
|
||||
isWideLayout: false,
|
||||
themeState: createThemeState(props)
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
computedStates: PropTypes.array,
|
||||
stagedActionIds: PropTypes.array,
|
||||
actionsById: PropTypes.object,
|
||||
currentStateIndex: PropTypes.number,
|
||||
monitorState: PropTypes.shape({
|
||||
initialScrollTop: PropTypes.number
|
||||
}),
|
||||
preserveScrollTop: PropTypes.bool,
|
||||
draggableActions: PropTypes.bool,
|
||||
stagedActions: PropTypes.array,
|
||||
select: PropTypes.func.isRequired,
|
||||
theme: PropTypes.oneOfType([
|
||||
PropTypes.object,
|
||||
PropTypes.string
|
||||
]),
|
||||
supportImmutable: PropTypes.bool,
|
||||
diffObjectHash: PropTypes.func,
|
||||
diffPropertyFilter: PropTypes.func,
|
||||
hideMainButtons: PropTypes.bool,
|
||||
hideActionButtons: PropTypes.bool
|
||||
};
|
||||
|
||||
static update = reducer;
|
||||
|
||||
static defaultProps = {
|
||||
select: (state) => state,
|
||||
supportImmutable: false,
|
||||
draggableActions: true,
|
||||
theme: 'inspector',
|
||||
invertTheme: true
|
||||
};
|
||||
|
||||
shouldComponentUpdate = shouldPureComponentUpdate;
|
||||
|
||||
componentDidMount() {
|
||||
this.updateSizeMode();
|
||||
this.updateSizeTimeout = setInterval(this.updateSizeMode.bind(this), 150);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.updateSizeTimeout);
|
||||
}
|
||||
|
||||
updateMonitorState = monitorState => {
|
||||
this.props.dispatch(updateMonitorState(monitorState));
|
||||
};
|
||||
|
||||
updateSizeMode() {
|
||||
const isWideLayout = this.refs.inspector.offsetWidth > 500;
|
||||
|
||||
if (isWideLayout !== this.state.isWideLayout) {
|
||||
this.setState({ isWideLayout });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let nextMonitorState = nextProps.monitorState;
|
||||
const monitorState = this.props.monitorState;
|
||||
|
||||
if (
|
||||
getCurrentActionId(this.props, monitorState) !==
|
||||
getCurrentActionId(nextProps, nextMonitorState) ||
|
||||
monitorState.startActionId !== nextMonitorState.startActionId ||
|
||||
monitorState.inspectedStatePath !== nextMonitorState.inspectedStatePath ||
|
||||
monitorState.inspectedActionPath !== nextMonitorState.inspectedActionPath ||
|
||||
this.props.computedStates !== nextProps.computedStates ||
|
||||
this.props.stagedActionIds !== nextProps.stagedActionIds
|
||||
) {
|
||||
this.setState(createIntermediateState(nextProps, nextMonitorState));
|
||||
}
|
||||
|
||||
if (this.props.theme !== nextProps.theme ||
|
||||
this.props.invertTheme !== nextProps.invertTheme) {
|
||||
this.setState({ themeState: createThemeState(nextProps) });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
stagedActionIds: actionIds, actionsById: actions, computedStates, draggableActions,
|
||||
tabs, invertTheme, skippedActionIds, currentStateIndex, monitorState, dataTypeKey,
|
||||
hideMainButtons, hideActionButtons
|
||||
} = this.props;
|
||||
const { selectedActionId, startActionId, searchValue, tabName } = monitorState;
|
||||
const inspectedPathType = tabName === 'Action' ? 'inspectedActionPath' : 'inspectedStatePath';
|
||||
const {
|
||||
themeState, isWideLayout, action, nextState, delta, error
|
||||
} = this.state;
|
||||
const { base16Theme, styling } = themeState;
|
||||
|
||||
return (
|
||||
<div key='inspector'
|
||||
ref='inspector'
|
||||
{...styling(['inspector', isWideLayout && 'inspectorWide'], isWideLayout)}>
|
||||
<ActionList {...{
|
||||
actions, actionIds, isWideLayout, searchValue, selectedActionId, startActionId,
|
||||
skippedActionIds, draggableActions, hideMainButtons, hideActionButtons, styling
|
||||
}}
|
||||
onSearch={this.handleSearch}
|
||||
onSelect={this.handleSelectAction}
|
||||
onToggleAction={this.handleToggleAction}
|
||||
onJumpToState={this.handleJumpToState}
|
||||
onCommit={this.handleCommit}
|
||||
onSweep={this.handleSweep}
|
||||
onReorderAction={this.handleReorderAction}
|
||||
currentActionId={actionIds[currentStateIndex]}
|
||||
lastActionId={getLastActionId(this.props)} />
|
||||
<ActionPreview {...{
|
||||
base16Theme, invertTheme, isWideLayout, tabs, tabName, delta, error, nextState,
|
||||
computedStates, action, actions, selectedActionId, startActionId, dataTypeKey
|
||||
}}
|
||||
monitorState={this.props.monitorState}
|
||||
updateMonitorState={this.updateMonitorState}
|
||||
styling={styling}
|
||||
onInspectPath={this.handleInspectPath.bind(this, inspectedPathType)}
|
||||
inspectedPath={monitorState[inspectedPathType]}
|
||||
onSelectTab={this.handleSelectTab} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleToggleAction = actionId => {
|
||||
this.props.dispatch(toggleAction(actionId));
|
||||
};
|
||||
|
||||
handleJumpToState = actionId => {
|
||||
if (jumpToAction) {
|
||||
this.props.dispatch(jumpToAction(actionId));
|
||||
} else { // Fallback for redux-devtools-instrument < 1.5
|
||||
const index = this.props.stagedActionIds.indexOf(actionId);
|
||||
if (index !== -1) this.props.dispatch(jumpToState(index));
|
||||
}
|
||||
};
|
||||
|
||||
handleReorderAction = (actionId, beforeActionId) => {
|
||||
if (reorderAction) this.props.dispatch(reorderAction(actionId, beforeActionId));
|
||||
};
|
||||
|
||||
handleCommit = () => {
|
||||
this.props.dispatch(commit());
|
||||
};
|
||||
|
||||
handleSweep = () => {
|
||||
this.props.dispatch(sweep());
|
||||
};
|
||||
|
||||
handleSearch = val => {
|
||||
this.updateMonitorState({ searchValue: val });
|
||||
};
|
||||
|
||||
handleSelectAction = (e, actionId) => {
|
||||
const { monitorState } = this.props;
|
||||
let startActionId;
|
||||
let selectedActionId;
|
||||
|
||||
if (e.shiftKey && monitorState.selectedActionId !== null) {
|
||||
if (monitorState.startActionId !== null) {
|
||||
if (actionId >= monitorState.startActionId) {
|
||||
startActionId = Math.min(monitorState.startActionId, monitorState.selectedActionId);
|
||||
selectedActionId = actionId;
|
||||
} else {
|
||||
selectedActionId = Math.max(monitorState.startActionId, monitorState.selectedActionId);
|
||||
startActionId = actionId;
|
||||
}
|
||||
} else {
|
||||
startActionId = Math.min(actionId, monitorState.selectedActionId);
|
||||
selectedActionId = Math.max(actionId, monitorState.selectedActionId);
|
||||
}
|
||||
} else {
|
||||
startActionId = null;
|
||||
if (actionId === monitorState.selectedActionId || monitorState.startActionId !== null) {
|
||||
selectedActionId = null;
|
||||
} else {
|
||||
selectedActionId = actionId;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateMonitorState({ startActionId, selectedActionId });
|
||||
};
|
||||
|
||||
handleInspectPath = (pathType, path) => {
|
||||
this.updateMonitorState({ [pathType]: path });
|
||||
};
|
||||
|
||||
handleSelectTab = tabName => {
|
||||
this.updateMonitorState({ tabName });
|
||||
};
|
||||
}
|
18
packages/redux-devtools-inspector/src/RightSlider.jsx
Normal file
18
packages/redux-devtools-inspector/src/RightSlider.jsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { PropTypes } from 'prop-types';
|
||||
|
||||
const RightSlider = ({ styling, shown, children, rotate }) =>
|
||||
(<div {...styling([
|
||||
'rightSlider',
|
||||
shown && 'rightSliderShown',
|
||||
rotate && 'rightSliderRotate',
|
||||
rotate && shown && 'rightSliderRotateShown'
|
||||
])}>
|
||||
{children}
|
||||
</div>);
|
||||
|
||||
RightSlider.propTypes = {
|
||||
shown: PropTypes.bool
|
||||
};
|
||||
|
||||
export default RightSlider;
|
29
packages/redux-devtools-inspector/src/createDiffPatcher.js
Normal file
29
packages/redux-devtools-inspector/src/createDiffPatcher.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { DiffPatcher } from 'jsondiffpatch/src/diffpatcher';
|
||||
|
||||
const defaultObjectHash = (o, idx) =>
|
||||
o === null && '$$null' ||
|
||||
o && (o.id || o.id === 0) && `$$id:${JSON.stringify(o.id)}` ||
|
||||
o && (o._id ||o._id === 0) && `$$_id:${JSON.stringify(o._id)}` ||
|
||||
'$$index:' + idx;
|
||||
|
||||
const defaultPropertyFilter = (name, context) =>
|
||||
typeof context.left[name] !== 'function' &&
|
||||
typeof context.right[name] !== 'function';
|
||||
|
||||
const defaultDiffPatcher = new DiffPatcher({
|
||||
arrays: { detectMove: false },
|
||||
objectHash: defaultObjectHash,
|
||||
propertyFilter: defaultPropertyFilter
|
||||
});
|
||||
|
||||
export default function createDiffPatcher(objectHash, propertyFilter) {
|
||||
if (!objectHash && !propertyFilter) {
|
||||
return defaultDiffPatcher;
|
||||
}
|
||||
|
||||
return new DiffPatcher({
|
||||
arrays: { detectMove: false },
|
||||
objectHash: objectHash || defaultObjectHash,
|
||||
propertyFilter: propertyFilter || defaultPropertyFilter
|
||||
});
|
||||
}
|
1
packages/redux-devtools-inspector/src/index.js
Normal file
1
packages/redux-devtools-inspector/src/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default from './DevtoolsInspector';
|
26
packages/redux-devtools-inspector/src/redux.js
Normal file
26
packages/redux-devtools-inspector/src/redux.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
const UPDATE_MONITOR_STATE = '@@redux-devtools-inspector/UPDATE_MONITOR_STATE';
|
||||
|
||||
export const DEFAULT_STATE = {
|
||||
selectedActionId: null,
|
||||
startActionId: null,
|
||||
inspectedActionPath: [],
|
||||
inspectedStatePath: [],
|
||||
tabName: 'Diff'
|
||||
};
|
||||
|
||||
export function updateMonitorState(monitorState) {
|
||||
return { type: UPDATE_MONITOR_STATE, monitorState };
|
||||
}
|
||||
|
||||
function reduceUpdateState(state, action) {
|
||||
return (action.type === UPDATE_MONITOR_STATE) ? {
|
||||
...state,
|
||||
...action.monitorState
|
||||
} : state;
|
||||
}
|
||||
|
||||
export function reducer(props, state=DEFAULT_STATE, action) {
|
||||
return {
|
||||
...reduceUpdateState(state, action)
|
||||
};
|
||||
}
|
18
packages/redux-devtools-inspector/src/tabs/ActionTab.jsx
Normal file
18
packages/redux-devtools-inspector/src/tabs/ActionTab.jsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import getItemString from './getItemString';
|
||||
import getJsonTreeTheme from './getJsonTreeTheme';
|
||||
|
||||
const ActionTab = ({
|
||||
action, styling, base16Theme, invertTheme, labelRenderer, dataTypeKey, isWideLayout
|
||||
}) =>
|
||||
(<JSONTree
|
||||
labelRenderer={labelRenderer}
|
||||
theme={getJsonTreeTheme(base16Theme)}
|
||||
data={action}
|
||||
getItemString={(type, data) => getItemString(styling, type, data, dataTypeKey, isWideLayout)}
|
||||
invertTheme={invertTheme}
|
||||
hideRoot
|
||||
/>);
|
||||
|
||||
export default ActionTab;
|
9
packages/redux-devtools-inspector/src/tabs/DiffTab.jsx
Normal file
9
packages/redux-devtools-inspector/src/tabs/DiffTab.jsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
import JSONDiff from './JSONDiff';
|
||||
|
||||
const DiffTab = ({ delta, styling, base16Theme, invertTheme, labelRenderer, isWideLayout }) =>
|
||||
(<JSONDiff
|
||||
{...{ delta, styling, base16Theme, invertTheme, labelRenderer, isWideLayout }}
|
||||
/>);
|
||||
|
||||
export default DiffTab;
|
126
packages/redux-devtools-inspector/src/tabs/JSONDiff.jsx
Normal file
126
packages/redux-devtools-inspector/src/tabs/JSONDiff.jsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import React, { Component } from 'react';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import stringify from 'javascript-stringify';
|
||||
import getItemString from './getItemString';
|
||||
import getJsonTreeTheme from './getJsonTreeTheme';
|
||||
|
||||
function stringifyAndShrink(val, isWideLayout) {
|
||||
if (val === null) { return 'null'; }
|
||||
|
||||
const str = stringify(val);
|
||||
if (typeof str === 'undefined') { return 'undefined'; }
|
||||
|
||||
if (isWideLayout) return str.length > 42 ? str.substr(0, 30) + '…' + str.substr(-10) : str;
|
||||
return str.length > 22 ? `${str.substr(0, 15)}…${str.substr(-5)}` : str;
|
||||
}
|
||||
|
||||
const expandFirstLevel = (keyName, data, level) => level <= 1;
|
||||
|
||||
function prepareDelta(value) {
|
||||
if (value && value._t === 'a') {
|
||||
const res = {};
|
||||
for (let key in value) {
|
||||
if (key !== '_t') {
|
||||
if (key[0] === '_' && !value[key.substr(1)]) {
|
||||
res[key.substr(1)] = value[key];
|
||||
} else if (value['_' + key]) {
|
||||
res[key] = [value['_' + key][0], value[key][0]];
|
||||
} else if (!value['_' + key] && key[0] !== '_') {
|
||||
res[key] = value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export default class JSONDiff extends Component {
|
||||
state = { data: {} }
|
||||
|
||||
componentDidMount() {
|
||||
this.updateData();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.delta !== this.props.delta) {
|
||||
this.updateData();
|
||||
}
|
||||
}
|
||||
|
||||
updateData() {
|
||||
// this magically fixes weird React error, where it can't find a node in tree
|
||||
// if we set `delta` as JSONTree data right away
|
||||
// https://github.com/alexkuz/redux-devtools-inspector/issues/17
|
||||
|
||||
this.setState({ data: this.props.delta });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { styling, base16Theme, ...props } = this.props;
|
||||
|
||||
if (!this.state.data) {
|
||||
return (
|
||||
<div {...styling('stateDiffEmpty')}>
|
||||
(states are equal)
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<JSONTree {...props}
|
||||
theme={getJsonTreeTheme(base16Theme)}
|
||||
data={this.state.data}
|
||||
getItemString={this.getItemString}
|
||||
valueRenderer={this.valueRenderer}
|
||||
postprocessValue={prepareDelta}
|
||||
isCustomNode={Array.isArray}
|
||||
shouldExpandNode={expandFirstLevel}
|
||||
hideRoot />
|
||||
);
|
||||
}
|
||||
|
||||
getItemString = (type, data) => (
|
||||
getItemString(
|
||||
this.props.styling, type, data, this.props.dataTypeKey, this.props.isWideLayout, true
|
||||
)
|
||||
)
|
||||
|
||||
valueRenderer = (raw, value) => {
|
||||
const { styling, isWideLayout } = this.props;
|
||||
|
||||
function renderSpan(name, body) {
|
||||
return (
|
||||
<span key={name} {...styling(['diff', name])}>{body}</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
switch(value.length) {
|
||||
case 1:
|
||||
return (
|
||||
<span {...styling('diffWrap')}>
|
||||
{renderSpan('diffAdd', stringifyAndShrink(value[0], isWideLayout))}
|
||||
</span>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<span {...styling('diffWrap')}>
|
||||
{renderSpan('diffUpdateFrom', stringifyAndShrink(value[0], isWideLayout))}
|
||||
{renderSpan('diffUpdateArrow', ' => ')}
|
||||
{renderSpan('diffUpdateTo', stringifyAndShrink(value[1], isWideLayout))}
|
||||
</span>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<span {...styling('diffWrap')}>
|
||||
{renderSpan('diffRemove', stringifyAndShrink(value[0]))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
}
|
18
packages/redux-devtools-inspector/src/tabs/StateTab.jsx
Normal file
18
packages/redux-devtools-inspector/src/tabs/StateTab.jsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import JSONTree from 'react-json-tree';
|
||||
import getItemString from './getItemString';
|
||||
import getJsonTreeTheme from './getJsonTreeTheme';
|
||||
|
||||
const StateTab = ({
|
||||
nextState, styling, base16Theme, invertTheme, labelRenderer, dataTypeKey, isWideLayout
|
||||
}) =>
|
||||
(<JSONTree
|
||||
labelRenderer={labelRenderer}
|
||||
theme={getJsonTreeTheme(base16Theme)}
|
||||
data={nextState}
|
||||
getItemString={(type, data) => getItemString(styling, type, data, dataTypeKey, isWideLayout)}
|
||||
invertTheme={invertTheme}
|
||||
hideRoot
|
||||
/>);
|
||||
|
||||
export default StateTab;
|
70
packages/redux-devtools-inspector/src/tabs/getItemString.js
Normal file
70
packages/redux-devtools-inspector/src/tabs/getItemString.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import React from 'react';
|
||||
import { Iterable } from 'immutable';
|
||||
import isIterable from '../utils/isIterable';
|
||||
|
||||
const IS_IMMUTABLE_KEY = '@@__IS_IMMUTABLE__@@';
|
||||
|
||||
function isImmutable(value) {
|
||||
return Iterable.isKeyed(value) || Iterable.isIndexed(value) || Iterable.isIterable(value);
|
||||
}
|
||||
|
||||
function getShortTypeString(val, diff) {
|
||||
if (diff && Array.isArray(val)) {
|
||||
val = val[val.length === 2 ? 1 : 0];
|
||||
}
|
||||
|
||||
if (isIterable(val) && !isImmutable(val)) {
|
||||
return '(…)';
|
||||
} else if (Array.isArray(val)) {
|
||||
return val.length > 0 ? '[…]' : '[]';
|
||||
} else if (val === null) {
|
||||
return 'null';
|
||||
} else if (val === undefined) {
|
||||
return 'undef';
|
||||
} else if (typeof val === 'object') {
|
||||
return Object.keys(val).length > 0 ? '{…}' : '{}';
|
||||
} else if (typeof val === 'function') {
|
||||
return 'fn';
|
||||
} else if (typeof val === 'string') {
|
||||
return `"${val.substr(0, 10) + (val.length > 10 ? '…' : '')}"`
|
||||
} else if (typeof val === 'symbol') {
|
||||
return 'symbol'
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function getText(type, data, isWideLayout, isDiff) {
|
||||
if (type === 'Object') {
|
||||
const keys = Object.keys(data);
|
||||
if (!isWideLayout) return keys.length ? '{…}' : '{}';
|
||||
|
||||
const str = keys
|
||||
.slice(0, 3)
|
||||
.map(key => `${key}: ${getShortTypeString(data[key], isDiff)}`)
|
||||
.concat(keys.length > 3 ? ['…'] : [])
|
||||
.join(', ');
|
||||
|
||||
return `{ ${str} }`;
|
||||
} else if (type === 'Array') {
|
||||
if (!isWideLayout) return data.length ? '[…]' : '[]';
|
||||
|
||||
const str = data
|
||||
.slice(0, 4)
|
||||
.map(val => getShortTypeString(val, isDiff))
|
||||
.concat(data.length > 4 ? ['…'] : []).join(', ');
|
||||
|
||||
return `[${str}]`;
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
const getItemString = (styling, type, data, dataTypeKey, isWideLayout, isDiff) =>
|
||||
(<span {...styling('treeItemHint')}>
|
||||
{data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''}
|
||||
{dataTypeKey && data[dataTypeKey] ? data[dataTypeKey] + ' ' : ''}
|
||||
{getText(type, data, isWideLayout, isDiff)}
|
||||
</span>);
|
||||
|
||||
export default getItemString;
|
|
@ -0,0 +1,17 @@
|
|||
export default function getJsonTreeTheme(base16Theme) {
|
||||
return {
|
||||
extend: base16Theme,
|
||||
nestedNode: ({ style }, keyPath, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
whiteSpace: expanded ? 'inherit' : 'nowrap'
|
||||
}
|
||||
}),
|
||||
nestedNodeItemString: ({ style }, keyPath, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
display: expanded ? 'none' : 'inline'
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
1
packages/redux-devtools-inspector/src/themes/index.js
Normal file
1
packages/redux-devtools-inspector/src/themes/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
export { default as inspector } from './inspector';
|
20
packages/redux-devtools-inspector/src/themes/inspector.js
Normal file
20
packages/redux-devtools-inspector/src/themes/inspector.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
scheme: 'inspector',
|
||||
author: 'Alexander Kuznetsov (alexkuz@gmail.com)',
|
||||
base00: '#181818',
|
||||
base01: '#282828',
|
||||
base02: '#383838',
|
||||
base03: '#585858',
|
||||
base04: '#b8b8b8',
|
||||
base05: '#d8d8d8',
|
||||
base06: '#e8e8e8',
|
||||
base07: '#FFFFFF',
|
||||
base08: '#E92F28',
|
||||
base09: '#dc9656',
|
||||
base0A: '#f7ca88',
|
||||
base0B: '#65AD00',
|
||||
base0C: '#86c1b9',
|
||||
base0D: '#347BD9',
|
||||
base0E: '#EC31C0',
|
||||
base0F: '#a16946'
|
||||
};
|
|
@ -0,0 +1,412 @@
|
|||
import jss from 'jss';
|
||||
import jssVendorPrefixer from 'jss-vendor-prefixer';
|
||||
import jssNested from 'jss-nested';
|
||||
import { createStyling } from 'react-base16-styling';
|
||||
import rgba from 'hex-rgba';
|
||||
import inspector from '../themes/inspector';
|
||||
import * as reduxThemes from 'redux-devtools-themes';
|
||||
import * as inspectorThemes from '../themes';
|
||||
|
||||
jss.use(jssVendorPrefixer());
|
||||
jss.use(jssNested());
|
||||
|
||||
|
||||
const colorMap = theme => ({
|
||||
TEXT_COLOR: theme.base06,
|
||||
TEXT_PLACEHOLDER_COLOR: rgba(theme.base06, 60),
|
||||
BACKGROUND_COLOR: theme.base00,
|
||||
SELECTED_BACKGROUND_COLOR: rgba(theme.base03, 20),
|
||||
SKIPPED_BACKGROUND_COLOR: rgba(theme.base03, 10),
|
||||
HEADER_BACKGROUND_COLOR: rgba(theme.base03, 30),
|
||||
HEADER_BORDER_COLOR: rgba(theme.base03, 20),
|
||||
BORDER_COLOR: rgba(theme.base03, 50),
|
||||
LIST_BORDER_COLOR: rgba(theme.base03, 50),
|
||||
ACTION_TIME_BACK_COLOR: rgba(theme.base03, 20),
|
||||
ACTION_TIME_COLOR: theme.base04,
|
||||
PIN_COLOR: theme.base04,
|
||||
ITEM_HINT_COLOR: rgba(theme.base0F, 90),
|
||||
TAB_BACK_SELECTED_COLOR: rgba(theme.base03, 20),
|
||||
TAB_BACK_COLOR: rgba(theme.base00, 70),
|
||||
TAB_BACK_HOVER_COLOR: rgba(theme.base03, 40),
|
||||
TAB_BORDER_COLOR: rgba(theme.base03, 50),
|
||||
DIFF_ADD_COLOR: rgba(theme.base0B, 40),
|
||||
DIFF_REMOVE_COLOR: rgba(theme.base08, 40),
|
||||
DIFF_ARROW_COLOR: theme.base0E,
|
||||
LINK_COLOR: rgba(theme.base0E, 90),
|
||||
LINK_HOVER_COLOR: theme.base0E,
|
||||
ERROR_COLOR: theme.base08,
|
||||
});
|
||||
|
||||
const getSheetFromColorMap = map => ({
|
||||
inspector: {
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
||||
'font-size': '12px',
|
||||
'font-smoothing': 'antialiased',
|
||||
'line-height': '1.5em',
|
||||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
color: map.TEXT_COLOR
|
||||
},
|
||||
|
||||
inspectorWide: {
|
||||
'flex-direction': 'row'
|
||||
},
|
||||
|
||||
actionList: {
|
||||
'flex-basis': '40%',
|
||||
'flex-shrink': 0,
|
||||
'overflow-x': 'hidden',
|
||||
'overflow-y': 'auto',
|
||||
'border-bottom-width': '3px',
|
||||
'border-bottom-style': 'double',
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
'border-color': map.LIST_BORDER_COLOR
|
||||
},
|
||||
|
||||
actionListHeader: {
|
||||
display: 'flex',
|
||||
flex: '0 0 auto',
|
||||
'align-items': 'center',
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
|
||||
'border-color': map.LIST_BORDER_COLOR
|
||||
},
|
||||
|
||||
actionListRows: {
|
||||
overflow: 'auto',
|
||||
|
||||
'& div.gu-transit': {
|
||||
opacity: '0.3'
|
||||
},
|
||||
|
||||
'& div.gu-mirror': {
|
||||
position: 'fixed',
|
||||
opacity: '0.8',
|
||||
height: 'auto !important',
|
||||
'border-width': '1px',
|
||||
'border-style': 'solid',
|
||||
'border-color': map.LIST_BORDER_COLOR
|
||||
},
|
||||
|
||||
'& div.gu-hide': {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
|
||||
actionListHeaderSelector: {
|
||||
display: 'inline-flex',
|
||||
'margin-right': '10px'
|
||||
},
|
||||
|
||||
actionListWide: {
|
||||
'flex-basis': '40%',
|
||||
'border-bottom': 'none',
|
||||
'border-right-width': '3px',
|
||||
'border-right-style': 'double'
|
||||
},
|
||||
|
||||
actionListItem: {
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
display: 'flex',
|
||||
'justify-content': 'space-between',
|
||||
padding: '5px 10px',
|
||||
cursor: 'pointer',
|
||||
'user-select': 'none',
|
||||
|
||||
'&:last-child': {
|
||||
'border-bottom-width': 0
|
||||
},
|
||||
|
||||
'border-bottom-color': map.BORDER_COLOR
|
||||
},
|
||||
|
||||
actionListItemSelected: {
|
||||
'background-color': map.SELECTED_BACKGROUND_COLOR
|
||||
},
|
||||
|
||||
actionListItemSkipped: {
|
||||
'background-color': map.SKIPPED_BACKGROUND_COLOR
|
||||
},
|
||||
|
||||
actionListFromFuture: {
|
||||
opacity: '0.6'
|
||||
},
|
||||
|
||||
actionListItemButtons: {
|
||||
position: 'relative',
|
||||
height: '20px',
|
||||
display: 'flex'
|
||||
},
|
||||
|
||||
actionListItemTime: {
|
||||
display: 'inline',
|
||||
padding: '4px 6px',
|
||||
'border-radius': '3px',
|
||||
'font-size': '0.8em',
|
||||
'line-height': '1em',
|
||||
'flex-shrink': 0,
|
||||
|
||||
'background-color': map.ACTION_TIME_BACK_COLOR,
|
||||
color: map.ACTION_TIME_COLOR
|
||||
},
|
||||
|
||||
actionListItemSelector: {
|
||||
display: 'inline-flex'
|
||||
},
|
||||
|
||||
actionListItemName: {
|
||||
overflow: 'hidden',
|
||||
'text-overflow': 'ellipsis',
|
||||
'line-height': '20px'
|
||||
},
|
||||
|
||||
actionListItemNameSkipped: {
|
||||
'text-decoration': 'line-through',
|
||||
opacity: 0.3
|
||||
},
|
||||
|
||||
actionListHeaderSearch: {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
padding: '5px 10px',
|
||||
'font-size': '1em',
|
||||
'font-family': 'monaco, Consolas, "Lucida Console", monospace',
|
||||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
color: map.TEXT_COLOR,
|
||||
|
||||
'&::-webkit-input-placeholder': {
|
||||
color: map.TEXT_PLACEHOLDER_COLOR
|
||||
},
|
||||
|
||||
'&::-moz-placeholder': {
|
||||
color: map.TEXT_PLACEHOLDER_COLOR
|
||||
}
|
||||
},
|
||||
|
||||
actionListHeaderWrapper: {
|
||||
position: 'relative',
|
||||
height: '20px'
|
||||
},
|
||||
|
||||
actionPreview: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
'flex-grow': 1,
|
||||
'overflow-y': 'hidden',
|
||||
|
||||
'& pre': {
|
||||
border: 'inherit',
|
||||
'border-radius': '3px',
|
||||
'line-height': 'inherit',
|
||||
color: 'inherit'
|
||||
},
|
||||
|
||||
'background-color': map.BACKGROUND_COLOR,
|
||||
},
|
||||
|
||||
actionPreviewContent: {
|
||||
flex: 1,
|
||||
'overflow-y': 'auto'
|
||||
},
|
||||
|
||||
stateDiff: {
|
||||
padding: '5px 0'
|
||||
},
|
||||
|
||||
stateDiffEmpty: {
|
||||
padding: '10px',
|
||||
|
||||
color: map.TEXT_PLACEHOLDER_COLOR
|
||||
},
|
||||
|
||||
stateError: {
|
||||
padding: '10px',
|
||||
'margin-left': '14px',
|
||||
'font-weight': 'bold',
|
||||
|
||||
color: map.ERROR_COLOR
|
||||
},
|
||||
|
||||
inspectedPath: {
|
||||
padding: '6px 0'
|
||||
},
|
||||
|
||||
inspectedPathKey: {
|
||||
'&:not(:last-child):after': {
|
||||
content: '" > "'
|
||||
}
|
||||
},
|
||||
|
||||
inspectedPathKeyLink: {
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
'text-decoration': 'underline'
|
||||
},
|
||||
|
||||
color: map.LINK_COLOR,
|
||||
'&:hover': {
|
||||
color: map.LINK_HOVER_COLOR
|
||||
}
|
||||
},
|
||||
|
||||
treeItemPin: {
|
||||
'font-size': '0.7em',
|
||||
'padding-left': '5px',
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
'text-decoration': 'underline'
|
||||
},
|
||||
|
||||
color: map.PIN_COLOR
|
||||
},
|
||||
|
||||
treeItemHint: {
|
||||
color: map.ITEM_HINT_COLOR
|
||||
},
|
||||
|
||||
previewHeader: {
|
||||
flex: '0 0 30px',
|
||||
padding: '5px 10px',
|
||||
'align-items': 'center',
|
||||
'border-bottom-width': '1px',
|
||||
'border-bottom-style': 'solid',
|
||||
|
||||
'background-color': map.HEADER_BACKGROUND_COLOR,
|
||||
'border-bottom-color': map.HEADER_BORDER_COLOR
|
||||
},
|
||||
|
||||
tabSelector: {
|
||||
position: 'relative',
|
||||
'z-index': 1,
|
||||
display: 'inline-flex',
|
||||
float: 'right'
|
||||
},
|
||||
|
||||
selectorButton: {
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
padding: '5px 10px',
|
||||
'border-style': 'solid',
|
||||
'border-width': '1px',
|
||||
'border-left-width': 0,
|
||||
|
||||
'&:first-child': {
|
||||
'border-left-width': '1px',
|
||||
'border-top-left-radius': '3px',
|
||||
'border-bottom-left-radius': '3px'
|
||||
},
|
||||
|
||||
'&:last-child': {
|
||||
'border-top-right-radius': '3px',
|
||||
'border-bottom-right-radius': '3px'
|
||||
},
|
||||
|
||||
'background-color': map.TAB_BACK_COLOR,
|
||||
|
||||
'&:hover': {
|
||||
'background-color': map.TAB_BACK_HOVER_COLOR
|
||||
},
|
||||
|
||||
'border-color': map.TAB_BORDER_COLOR
|
||||
},
|
||||
|
||||
selectorButtonSmall: {
|
||||
padding: '0px 8px',
|
||||
'font-size': '0.8em'
|
||||
},
|
||||
|
||||
selectorButtonSelected: {
|
||||
'background-color': map.TAB_BACK_SELECTED_COLOR
|
||||
},
|
||||
|
||||
diff: {
|
||||
padding: '2px 3px',
|
||||
'border-radius': '3px',
|
||||
position: 'relative',
|
||||
|
||||
color: map.TEXT_COLOR
|
||||
},
|
||||
|
||||
diffWrap: {
|
||||
position: 'relative',
|
||||
'z-index': 1
|
||||
},
|
||||
|
||||
diffAdd: {
|
||||
'background-color': map.DIFF_ADD_COLOR
|
||||
},
|
||||
|
||||
diffRemove: {
|
||||
'text-decoration': 'line-through',
|
||||
'background-color': map.DIFF_REMOVE_COLOR
|
||||
},
|
||||
|
||||
diffUpdateFrom: {
|
||||
'text-decoration': 'line-through',
|
||||
'background-color': map.DIFF_REMOVE_COLOR
|
||||
},
|
||||
|
||||
diffUpdateTo: {
|
||||
'background-color': map.DIFF_ADD_COLOR
|
||||
},
|
||||
|
||||
diffUpdateArrow: {
|
||||
color: map.DIFF_ARROW_COLOR
|
||||
},
|
||||
|
||||
rightSlider: {
|
||||
'font-smoothing': 'subpixel-antialiased', // http://stackoverflow.com/a/21136111/4218591
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
transform: 'translateX(150%)',
|
||||
transition: 'transform 0.2s ease-in-out'
|
||||
},
|
||||
|
||||
rightSliderRotate: {
|
||||
transform: 'rotateX(90deg)',
|
||||
transition: 'transform 0.2s ease-in-out 0.08s'
|
||||
},
|
||||
|
||||
rightSliderShown: {
|
||||
position: 'static',
|
||||
transform: 'translateX(0)',
|
||||
},
|
||||
|
||||
rightSliderRotateShown: {
|
||||
transform: 'rotateX(0)',
|
||||
transition: 'transform 0.2s ease-in-out 0.18s'
|
||||
}
|
||||
});
|
||||
|
||||
let themeSheet;
|
||||
|
||||
const getDefaultThemeStyling = theme => {
|
||||
if (themeSheet) {
|
||||
themeSheet.detach();
|
||||
}
|
||||
|
||||
themeSheet = jss.createStyleSheet(
|
||||
getSheetFromColorMap(colorMap(theme))
|
||||
).attach();
|
||||
|
||||
return themeSheet.classes;
|
||||
};
|
||||
|
||||
export const base16Themes = { ...reduxThemes, ...inspectorThemes };
|
||||
|
||||
export const createStylingFromTheme = createStyling(getDefaultThemeStyling, {
|
||||
defaultBase16: inspector,
|
||||
base16Themes
|
||||
});
|
29
packages/redux-devtools-inspector/src/utils/deepMap.js
Normal file
29
packages/redux-devtools-inspector/src/utils/deepMap.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
function deepMapCached(obj, f, ctx, cache) {
|
||||
cache.push(obj);
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(function(val, key) {
|
||||
val = f.call(ctx, val, key);
|
||||
return (typeof val === 'object' && cache.indexOf(val) === -1) ?
|
||||
deepMapCached(val, f, ctx, cache) : val;
|
||||
});
|
||||
} else if (typeof obj === 'object') {
|
||||
const res = {};
|
||||
for (const key in obj) {
|
||||
let val = obj[key];
|
||||
if (val && typeof val === 'object') {
|
||||
val = f.call(ctx, val, key);
|
||||
res[key] = cache.indexOf(val) === -1 ?
|
||||
deepMapCached(val, f, ctx, cache) : val;
|
||||
} else {
|
||||
res[key] = f.call(ctx, val, key);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
export default function deepMap(obj, f, ctx) {
|
||||
return deepMapCached(obj, f, ctx, []);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { Iterable, fromJS } from 'immutable';
|
||||
import isIterable from './isIterable';
|
||||
|
||||
function iterateToKey(obj, key) { // maybe there's a better way, dunno
|
||||
let idx = 0;
|
||||
for (let entry of obj) {
|
||||
if (Array.isArray(entry)) {
|
||||
if (entry[0] === key) return entry[1];
|
||||
} else {
|
||||
if (idx > key) return;
|
||||
if (idx === key) return entry;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
export default function getInspectedState(state, path, convertImmutable) {
|
||||
state = path && path.length ?
|
||||
{
|
||||
[path[path.length - 1]]: path.reduce(
|
||||
(s, key) => {
|
||||
if (!s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (Iterable.isAssociative(s)) {
|
||||
return s.get(key);
|
||||
} else if (isIterable(s)) {
|
||||
return iterateToKey(s, key);
|
||||
}
|
||||
|
||||
return s[key];
|
||||
},
|
||||
state
|
||||
)
|
||||
} : state;
|
||||
|
||||
if (convertImmutable) {
|
||||
try {
|
||||
state = fromJS(state).toJS();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export default function isIterable(obj) {
|
||||
return obj !== null && typeof obj === 'object' && !Array.isArray(obj) &&
|
||||
typeof obj[window.Symbol.iterator] === 'function';
|
||||
}
|
76
packages/redux-devtools-inspector/webpack.config.js
Normal file
76
packages/redux-devtools-inspector/webpack.config.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||
var ExportFilesWebpackPlugin = require('export-files-webpack-plugin');
|
||||
var NyanProgressWebpackPlugin = require('nyan-progress-webpack-plugin');
|
||||
|
||||
var pkg = require('./package.json');
|
||||
|
||||
var isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
devtool: 'eval',
|
||||
entry: isProduction ?
|
||||
[ './demo/src/js/index' ] :
|
||||
[
|
||||
'webpack-dev-server/client?http://localhost:3000',
|
||||
'webpack/hot/only-dev-server',
|
||||
'./demo/src/js/index'
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, 'demo/dist'),
|
||||
filename: 'js/bundle.js',
|
||||
hash: true
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(isProduction ? ['demo/dist'] : []),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
template: 'demo/src/index.html',
|
||||
filename: 'index.html',
|
||||
package: pkg
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
|
||||
},
|
||||
}),
|
||||
new webpack.NoErrorsPlugin(),
|
||||
new NyanProgressWebpackPlugin()
|
||||
].concat(isProduction ? [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: { warnings: false },
|
||||
output: { comments: false }
|
||||
})
|
||||
] : [
|
||||
new ExportFilesWebpackPlugin('demo/dist/index.html'),
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
]),
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.jsx']
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.jsx?$/,
|
||||
loaders: ['babel'],
|
||||
include: [
|
||||
path.join(__dirname, 'src'),
|
||||
path.join(__dirname, 'demo/src/js')
|
||||
]
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json'
|
||||
}]
|
||||
},
|
||||
devServer: isProduction ? null : {
|
||||
quiet: false,
|
||||
port: 3000,
|
||||
hot: true,
|
||||
stats: {
|
||||
chunkModules: false,
|
||||
colors: true
|
||||
},
|
||||
historyApiFallback: true
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user