mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-02-07 15:10:45 +03:00
Merge react-json-tree package (#428)
* Merge react-json-tree from alexkuz/react-json-tree * Npm package config * Add credits * Stick `eslint-plugin-react` to `7.4.0` till change deprecated `componentWillReceiveProps`
This commit is contained in:
parent
a77b3ad46c
commit
b80cc9e5b9
|
@ -2,6 +2,7 @@
|
|||
"private": true,
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.0.0",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"lerna": "3.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
16
packages/react-json-tree/.babelrc
Normal file
16
packages/react-json-tree/.babelrc
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"plugins": [
|
||||
"transform-runtime",
|
||||
"transform-es3-property-literals",
|
||||
"transform-es3-member-expression-literals",
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
],
|
||||
"presets": [[
|
||||
"env",
|
||||
{
|
||||
"loose": true,
|
||||
"shippedProposals": true
|
||||
}
|
||||
], "react"]
|
||||
}
|
5
packages/react-json-tree/.eslintignore
Normal file
5
packages/react-json-tree/.eslintignore
Normal file
|
@ -0,0 +1,5 @@
|
|||
lib
|
||||
**/node_modules
|
||||
**/webpack.config.js
|
||||
examples/**/server.js
|
||||
examples/src/App.js
|
53
packages/react-json-tree/.eslintrc
Normal file
53
packages/react-json-tree/.eslintrc
Normal file
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"standard",
|
||||
"plugin:react/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-syntax": 0,
|
||||
"comma-dangle": 0,
|
||||
"no-param-reassign": 0,
|
||||
"space-infix-ops": 0,
|
||||
"react/sort-comp": [
|
||||
1, {
|
||||
"order": [
|
||||
"static-methods",
|
||||
"constructor",
|
||||
"lifecycle",
|
||||
"everything-else",
|
||||
"render",
|
||||
"/^handle.+$/"
|
||||
],
|
||||
"groups": {
|
||||
"lifecycle": [
|
||||
"childContextTypes",
|
||||
"getInitialState",
|
||||
"state",
|
||||
"getChildContext",
|
||||
"componentWillMount",
|
||||
"componentDidMount",
|
||||
"componentWillReceiveProps",
|
||||
"shouldComponentUpdate",
|
||||
"componentWillUpdate",
|
||||
"componentDidUpdate",
|
||||
"componentWillUnmount"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
"prettier",
|
||||
"standard",
|
||||
"react",
|
||||
"babel"
|
||||
]
|
||||
}
|
22
packages/react-json-tree/LICENSE.md
Normal file
22
packages/react-json-tree/LICENSE.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Shusaku Uesugi, (c) 2016-present 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.
|
155
packages/react-json-tree/README.md
Normal file
155
packages/react-json-tree/README.md
Normal file
|
@ -0,0 +1,155 @@
|
|||
# react-json-tree
|
||||
|
||||
React JSON Viewer Component, Extracted from [redux-devtools](https://github.com/reduxjs/redux-devtools). Supports [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterable) objects, such as [Immutable.js](https://facebook.github.io/immutable-js/).
|
||||
|
||||
![](https://img.shields.io/npm/v/react-json-tree.svg)
|
||||
|
||||
### Usage
|
||||
|
||||
```jsx
|
||||
import JSONTree from 'react-json-tree'
|
||||
// If you're using Immutable.js: `npm i --save immutable`
|
||||
import { Map } from 'immutable'
|
||||
|
||||
// Inside a React component:
|
||||
const json = {
|
||||
array: [1, 2, 3],
|
||||
bool: true,
|
||||
object: {
|
||||
foo: 'bar'
|
||||
},
|
||||
immutable: Map({ key: 'value' })
|
||||
}
|
||||
|
||||
<JSONTree data={json} />
|
||||
```
|
||||
|
||||
#### Result:
|
||||
|
||||
![](http://cl.ly/image/3f2C2k2t3D0o/screenshot%202015-08-26%20at%2010.24.12%20AM.png)
|
||||
|
||||
Check out [examples](examples) directory for more details.
|
||||
|
||||
### Theming
|
||||
|
||||
This component now uses [react-base16-styling](https://github.com/alexkuz/react-base16-styling) module, which allows to customize component via `theme` property, which can be the following:
|
||||
- [base16](http://chriskempson.github.io/base16) theme data. [The example theme data can be found here](https://github.com/gaearon/redux-devtools/tree/75322b15ee7ba03fddf10ac3399881e302848874/src/react/themes).
|
||||
- object that contains style objects, strings (that treated as classnames) or functions. A function is used to extend its first argument `{ style, className }` and should return an object with the same structure. Other arguments depend on particular context (and should be described here). See [createStylingFromTheme.js](https://github.com/alexkuz/react-json-tree/blob/feature-refactor-styling/src/createStylingFromTheme.js) for the list of styling object keys. Also, this object can extend `base16` theme via `extend` property.
|
||||
|
||||
Every theme has a light version, which is enabled with `invertTheme` prop.
|
||||
|
||||
```jsx
|
||||
const theme = {
|
||||
scheme: 'monokai',
|
||||
author: 'wimer hazenberg (http://www.monokai.nl)',
|
||||
base00: '#272822',
|
||||
base01: '#383830',
|
||||
base02: '#49483e',
|
||||
base03: '#75715e',
|
||||
base04: '#a59f85',
|
||||
base05: '#f8f8f2',
|
||||
base06: '#f5f4f1',
|
||||
base07: '#f9f8f5',
|
||||
base08: '#f92672',
|
||||
base09: '#fd971f',
|
||||
base0A: '#f4bf75',
|
||||
base0B: '#a6e22e',
|
||||
base0C: '#a1efe4',
|
||||
base0D: '#66d9ef',
|
||||
base0E: '#ae81ff',
|
||||
base0F: '#cc6633'
|
||||
};
|
||||
|
||||
<div>
|
||||
<JSONTree data={data} theme={theme} invertTheme={false} />
|
||||
</div>
|
||||
|
||||
```
|
||||
|
||||
#### Result (Monokai theme, dark background):
|
||||
|
||||
![](http://cl.ly/image/330o2L1J3V0h/screenshot%202015-08-26%20at%2010.48.24%20AM.png)
|
||||
|
||||
#### Advanced Customization
|
||||
|
||||
```jsx
|
||||
<div>
|
||||
<JSONTree data={data} theme={{
|
||||
extend: theme,
|
||||
// underline keys for literal values
|
||||
valueLabel: {
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
// switch key for objects to uppercase when object is expanded.
|
||||
// `nestedNodeLabel` receives additional arguments `expanded` and `keyPath`
|
||||
nestedNodeLabel: ({ style }, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
textTransform: expanded ? 'uppercase' : style.textTransform
|
||||
}
|
||||
})
|
||||
}} />
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Customize Labels for Arrays, Objects, and Iterables
|
||||
|
||||
You can pass `getItemString` to customize the way arrays, objects, and iterable nodes are displayed (optional).
|
||||
|
||||
By default, it'll be:
|
||||
|
||||
```jsx
|
||||
<JSONTree getItemString={(type, data, itemType, itemString)
|
||||
=> <span>{itemType} {itemString}</span>}
|
||||
```
|
||||
|
||||
But if you pass the following:
|
||||
|
||||
```jsx
|
||||
const getItemString = (type, data, itemType, itemString)
|
||||
=> (<span> // {type}</span>);
|
||||
```
|
||||
|
||||
Then the preview of child elements now look like this:
|
||||
|
||||
![](http://cl.ly/image/1J1a0b0T0K3c/screenshot%202015-10-07%20at%203.44.31%20PM.png)
|
||||
|
||||
#### Customize Rendering
|
||||
|
||||
You can pass the following properties to customize rendered labels and values:
|
||||
|
||||
```jsx
|
||||
<JSONTree
|
||||
labelRenderer={raw => <strong>{raw}</strong>}
|
||||
valueRenderer={raw => <em>{raw}</em>}
|
||||
/>
|
||||
```
|
||||
|
||||
In this example the label and value will be rendered with `<strong>` and `<em>` wrappers respectively.
|
||||
|
||||
For `labelRenderer`, you can provide a full path - [see this PR](https://github.com/chibicode/react-json-tree/pull/32).
|
||||
|
||||
#### More Options
|
||||
|
||||
- `shouldExpandNode: function(keyName, data, level)` - determines if node should be expanded (root is expanded by default)
|
||||
- `hideRoot: Boolean` - if `true`, the root node is hidden.
|
||||
- `sortObjectKeys: Boolean | function(a, b)` - sorts object keys with compare function (optional). Isn't applied to iterable maps like `Immutable.Map`.
|
||||
|
||||
### Credits
|
||||
|
||||
- All credits to [Dave Vedder](http://www.eskimospy.com/) ([veddermatic@gmail.com](mailto:veddermatic@gmail.com)), who wrote the original code as [JSONViewer](https://bitbucket.org/davevedder/react-json-viewer/).
|
||||
- Extracted from [redux-devtools](https://github.com/gaearon/redux-devtools), which contained ES6 + inline style port of [JSONViewer](https://bitbucket.org/davevedder/react-json-viewer/) by [Daniele Zannotti](http://www.github.com/dzannotti) ([dzannotti@me.com](mailto:dzannotti@me.com))
|
||||
- [Iterable support](https://github.com/gaearon/redux-devtools/pull/79) thanks to [Daniel K](https://github.com/FredyC).
|
||||
- npm package created by [Shu Uesugi](http://github.com/chibicode) ([shu@chibicode.com](mailto:shu@chibicode.com)) per [this issue](https://github.com/gaearon/redux-devtools/issues/85).
|
||||
- Improved and maintained by [Alexander Kuznetsov](https://github.com/alexkuz). The repository was merged into [`redux-devtools` monorepo](https://github.com/reduxjs/redux-devtools) from [`alexkuz/react-json-tree`](https://github.com/alexkuz/react-json-tree).
|
||||
|
||||
### Similar Libraries
|
||||
|
||||
- [react-treeview](https://github.com/chenglou/react-treeview)
|
||||
- [react-json-inspector](https://github.com/Lapple/react-json-inspector)
|
||||
- [react-object-inspector](https://github.com/xyc/react-object-inspector)
|
||||
- [react-json-view](https://github.com/mac-s-g/react-json-view)
|
||||
|
||||
### License
|
||||
|
||||
MIT
|
9
packages/react-json-tree/examples/.babelrc
Executable file
9
packages/react-json-tree/examples/.babelrc
Executable file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
],
|
||||
"presets": [
|
||||
"env", "react"
|
||||
]
|
||||
}
|
25
packages/react-json-tree/examples/.eslintrc
Executable file
25
packages/react-json-tree/examples/.eslintrc
Executable file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"standard",
|
||||
"plugin:react/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"quotes": [2, "single"],
|
||||
"strict": [2, "never"],
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/react-in-jsx-scope": 2
|
||||
},
|
||||
"plugins": [
|
||||
"prettier",
|
||||
"standard",
|
||||
"react"
|
||||
]
|
||||
}
|
21
packages/react-json-tree/examples/LICENSE
Executable file
21
packages/react-json-tree/examples/LICENSE
Executable file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Dan Abramov
|
||||
|
||||
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.
|
47
packages/react-json-tree/examples/README.md
Executable file
47
packages/react-json-tree/examples/README.md
Executable file
|
@ -0,0 +1,47 @@
|
|||
react-hot-boilerplate
|
||||
=====================
|
||||
|
||||
The minimal dev environment to enable live-editing React components.
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
open http://localhost:3000
|
||||
```
|
||||
|
||||
Now edit `src/App.js`.
|
||||
Your changes will appear without reloading the browser like in [this video](http://vimeo.com/100010922).
|
||||
|
||||
### Linting
|
||||
|
||||
This boilerplate project includes React-friendly ESLint configuration.
|
||||
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Using `0.0.0.0` as Host
|
||||
|
||||
You may want to change the host in `server.js` and `webpack.config.js` from `localhost` to `0.0.0.0` to allow access from same WiFi network. This is not enabled by default because it is reported to cause problems on Windows. This may also be useful if you're using a VM.
|
||||
|
||||
### Missing Features
|
||||
|
||||
This boilerplate is purposefully simple to show the minimal configuration for React Hot Loader. For a real project, you'll want to add a separate config for production with hot reloading disabled and minification enabled. You'll also want to add a router, styles and maybe combine dev server with an existing server. This is out of scope of this boilerplate, but you may want to look into [other starter kits](https://github.com/gaearon/react-hot-loader/blob/master/docs/README.md#starter-kits).
|
||||
|
||||
### Dependencies
|
||||
|
||||
* React
|
||||
* Webpack
|
||||
* [webpack-dev-server](https://github.com/webpack/webpack-dev-server)
|
||||
* [babel-loader](https://github.com/babel/babel-loader)
|
||||
* [react-hot-loader](https://github.com/gaearon/react-hot-loader)
|
||||
|
||||
### Resources
|
||||
|
||||
* [Demo video](http://vimeo.com/100010922)
|
||||
* [react-hot-loader on Github](https://github.com/gaearon/react-hot-loader)
|
||||
* [Integrating JSX live reload into your workflow](http://gaearon.github.io/react-hot-loader/getstarted/)
|
||||
* [Troubleshooting guide](https://github.com/gaearon/react-hot-loader/blob/master/docs/Troubleshooting.md)
|
||||
* Ping dan_abramov on Twitter or #reactjs IRC
|
17
packages/react-json-tree/examples/index.html
Executable file
17
packages/react-json-tree/examples/index.html
Executable file
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Sample App</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: SF UI Text,Roboto,Helvetica Neue,Helvetica,sans-serif;
|
||||
font-size: 18px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'>
|
||||
</div>
|
||||
<script src="/static/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
51
packages/react-json-tree/examples/package.json
Normal file
51
packages/react-json-tree/examples/package.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "react-json-tree-example",
|
||||
"version": "1.0.0",
|
||||
"description": "React-Json-Tree example",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"lint": "eslint src",
|
||||
"stats": "NODE_ENV=production webpack --json > dist/stats.json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gaearon/react-hot-boilerplate.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"reactjs",
|
||||
"boilerplate",
|
||||
"hot",
|
||||
"reload",
|
||||
"hmr",
|
||||
"live",
|
||||
"edit",
|
||||
"webpack"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaearon/react-hot-boilerplate/issues"
|
||||
},
|
||||
"homepage": "https://github.com/gaearon/react-hot-boilerplate",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"eslint": "^4.10.0",
|
||||
"eslint-plugin-babel": "^4.1.2",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-dev-server": "^2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"immutable": "^3.8.1",
|
||||
"react": "^16.0.0",
|
||||
"react-base16-styling": "^0.5.3",
|
||||
"react-dom": "^16.0.0"
|
||||
}
|
||||
}
|
15
packages/react-json-tree/examples/server.js
vendored
Executable file
15
packages/react-json-tree/examples/server.js
vendored
Executable file
|
@ -0,0 +1,15 @@
|
|||
var webpack = require('webpack');
|
||||
var WebpackDevServer = require('webpack-dev-server');
|
||||
var config = require('./webpack.config');
|
||||
|
||||
new WebpackDevServer(webpack(config), {
|
||||
publicPath: config.output.publicPath,
|
||||
hot: true,
|
||||
historyApiFallback: true
|
||||
}).listen(3000, 'localhost', function (err, result) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
console.log('Listening at localhost:3000');
|
||||
});
|
193
packages/react-json-tree/examples/src/App.js
vendored
Executable file
193
packages/react-json-tree/examples/src/App.js
vendored
Executable file
|
@ -0,0 +1,193 @@
|
|||
import React from 'react';
|
||||
import { Map } from 'immutable';
|
||||
import JSONTree from '../../src';
|
||||
|
||||
const getLabelStyle = ({ style }, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
textTransform: expanded ? 'uppercase' : style.textTransform
|
||||
}
|
||||
});
|
||||
|
||||
const getBoolStyle = ({ style }, nodeType) => ({
|
||||
style: {
|
||||
...style,
|
||||
border: nodeType === 'Boolean' ? '1px solid #DD3333' : style.border,
|
||||
borderRadius: nodeType === 'Boolean' ? 3 : style.borderRadius
|
||||
}
|
||||
});
|
||||
|
||||
const getItemString = type => (
|
||||
<span>
|
||||
{' // '}
|
||||
{type}
|
||||
</span>
|
||||
);
|
||||
|
||||
const getValueLabelStyle = ({ style }, nodeType, keyPath) => ({
|
||||
style: {
|
||||
...style,
|
||||
color:
|
||||
!Number.isNaN(keyPath[0]) && !(parseInt(keyPath, 10) % 2)
|
||||
? '#33F'
|
||||
: style.color
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const longString =
|
||||
'Loremipsumdolorsitamet,consecteturadipiscingelit.Namtempusipsumutfelisdignissimauctor.Maecenasodiolectus,finibusegetultricesvel,aliquamutelit.Loremipsumdolorsitamet,consecteturadipiscingelit.Namtempusipsumutfelisdignissimauctor.Maecenasodiolectus,finibusegetultricesvel,aliquamutelit.Loremipsumdolorsitamet,consecteturadipiscingelit.Namtempusipsumutfelisdignissimauctor.Maecenasodiolectus,finibusegetultricesvel,aliquamutelit.';
|
||||
|
||||
const Custom = function(value) {
|
||||
this.value = value;
|
||||
};
|
||||
Custom.prototype[Symbol.toStringTag] = 'Custom';
|
||||
|
||||
const data = {
|
||||
array: [1, 2, 3],
|
||||
emptyArray: [],
|
||||
bool: true,
|
||||
date: new Date(),
|
||||
error: new Error(longString),
|
||||
object: {
|
||||
foo: {
|
||||
bar: 'baz',
|
||||
nested: {
|
||||
moreNested: {
|
||||
evenMoreNested: {
|
||||
veryNested: {
|
||||
insanelyNested: {
|
||||
ridiculouslyDeepValue: 'Hello'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
baz: undefined,
|
||||
func: function User() {}
|
||||
},
|
||||
emptyObject: {},
|
||||
symbol: Symbol('value'),
|
||||
// eslint-disable-next-line new-cap
|
||||
immutable: Map([
|
||||
['key', 'value'],
|
||||
[{ objectKey: 'value' }, { objectKey: 'value' }]
|
||||
]),
|
||||
map: new window.Map([
|
||||
['key', 'value'],
|
||||
[0, 'value'],
|
||||
[{ objectKey: 'value' }, { objectKey: 'value' }]
|
||||
]),
|
||||
weakMap: new window.WeakMap([
|
||||
[{ objectKey: 'value' }, { objectKey: 'value' }]
|
||||
]),
|
||||
set: new window.Set(['value', 0, { objectKey: 'value' }]),
|
||||
weakSet: new window.WeakSet([
|
||||
{ objectKey: 'value1' },
|
||||
{ objectKey: 'value2' }
|
||||
]),
|
||||
hugeArray: Array.from({ length: 10000 }).map((_, i) => `item #${i}`),
|
||||
customProfile: {
|
||||
avatar: new Custom('placehold.it/50x50'),
|
||||
name: new Custom('Name')
|
||||
},
|
||||
longString
|
||||
};
|
||||
|
||||
const theme = {
|
||||
scheme: 'monokai',
|
||||
author: 'wimer hazenberg (http://www.monokai.nl)',
|
||||
base00: '#272822',
|
||||
base01: '#383830',
|
||||
base02: '#49483e',
|
||||
base03: '#75715e',
|
||||
base04: '#a59f85',
|
||||
base05: '#f8f8f2',
|
||||
base06: '#f5f4f1',
|
||||
base07: '#f9f8f5',
|
||||
base08: '#f92672',
|
||||
base09: '#fd971f',
|
||||
base0A: '#f4bf75',
|
||||
base0B: '#a6e22e',
|
||||
base0C: '#a1efe4',
|
||||
base0D: '#66d9ef',
|
||||
base0E: '#ae81ff',
|
||||
base0F: '#cc6633'
|
||||
};
|
||||
|
||||
const App = () => (
|
||||
<div>
|
||||
<JSONTree data={data} theme={theme} invertTheme />
|
||||
<br />
|
||||
<h3>Dark Theme</h3>
|
||||
<JSONTree data={data} theme={theme} invertTheme={false} />
|
||||
<br />
|
||||
<h3>Hidden Root</h3>
|
||||
<JSONTree data={data} theme={theme} hideRoot />
|
||||
<br />
|
||||
<h3>Base16 Greenscreen Theme</h3>
|
||||
<JSONTree data={data} theme="greenscreen" invertTheme={false} />
|
||||
<h4>Inverted Theme</h4>
|
||||
<JSONTree data={data} theme="greenscreen" invertTheme />
|
||||
<br />
|
||||
<h3>Style Customization</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Label changes between uppercase/lowercase based on the expanded state.
|
||||
</li>
|
||||
<li>Array keys are styled based on their parity.</li>
|
||||
<li>
|
||||
The labels of objects, arrays, and iterables are customized as "//
|
||||
type".
|
||||
</li>
|
||||
<li>See code for details.</li>
|
||||
</ul>
|
||||
<div>
|
||||
<JSONTree
|
||||
data={data}
|
||||
theme={{
|
||||
extend: theme,
|
||||
nestedNodeLabel: getLabelStyle,
|
||||
value: getBoolStyle,
|
||||
valueLabel: getValueLabelStyle
|
||||
}}
|
||||
getItemString={getItemString}
|
||||
/>
|
||||
</div>
|
||||
<h3>More Fine Grained Rendering</h3>
|
||||
<p>
|
||||
Pass <code>labelRenderer</code> or <code>valueRenderer</code>.
|
||||
</p>
|
||||
<div>
|
||||
<JSONTree
|
||||
data={data}
|
||||
theme={theme}
|
||||
labelRenderer={([raw]) => <span>(({raw})):</span>}
|
||||
valueRenderer={raw => (
|
||||
<em>
|
||||
<span role="img" aria-label="mellow">
|
||||
😐
|
||||
</span>{' '}
|
||||
{raw}{' '}
|
||||
<span role="img" aria-label="mellow">
|
||||
😐
|
||||
</span>
|
||||
</em>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
Sort object keys with <code>sortObjectKeys</code> prop.
|
||||
</p>
|
||||
<div>
|
||||
<JSONTree data={data} theme={theme} sortObjectKeys />
|
||||
</div>
|
||||
<p>Collapsed root node</p>
|
||||
<div>
|
||||
<JSONTree data={data} theme={theme} shouldExpandNode={() => false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
5
packages/react-json-tree/examples/src/index.js
vendored
Executable file
5
packages/react-json-tree/examples/src/index.js
vendored
Executable file
|
@ -0,0 +1,5 @@
|
|||
import { render } from 'react-dom';
|
||||
import React from 'react';
|
||||
import App from './App';
|
||||
|
||||
render(<App />, document.getElementById('root'));
|
51
packages/react-json-tree/examples/webpack.config.js
Executable file
51
packages/react-json-tree/examples/webpack.config.js
Executable file
|
@ -0,0 +1,51 @@
|
|||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
|
||||
var isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
entry: [
|
||||
!isProduction && 'webpack-dev-server/client?http://localhost:3000',
|
||||
!isProduction && 'webpack/hot/only-dev-server',
|
||||
'./src/index'
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/static/'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
|
||||
}
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
isProduction && new webpack.optimize.UglifyJsPlugin({
|
||||
compress: { warnings: false },
|
||||
output: { comments: false },
|
||||
sourceMap: true
|
||||
})
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
alias: {
|
||||
'react-json-tree/lib': path.join(__dirname, '..', 'src'),
|
||||
'react-json-tree': path.join(__dirname, '..', 'src'),
|
||||
'react': path.join(__dirname, 'node_modules', 'react')
|
||||
},
|
||||
extensions: ['.js']
|
||||
},
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
loaders: ['babel-loader'].filter(Boolean),
|
||||
include: path.join(__dirname, 'src')
|
||||
}, {
|
||||
test: /\.js$/,
|
||||
loaders: ['babel-loader'].filter(Boolean),
|
||||
include: path.join(__dirname, '..', 'src')
|
||||
}]
|
||||
}
|
||||
};
|
85
packages/react-json-tree/package.json
Normal file
85
packages/react-json-tree/package.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"name": "react-json-tree",
|
||||
"version": "0.11.1",
|
||||
"description": "React JSON Viewer Component, Extracted from redux-devtools",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf lib",
|
||||
"build": "babel src --out-dir lib",
|
||||
"lint": "eslint --max-warnings=0 src test examples/src",
|
||||
"test":
|
||||
"npm run lint && NODE_ENV=test mocha --compilers js:babel-core/register --recursive",
|
||||
"test:watch":
|
||||
"NODE_ENV=test mocha --compilers js:babel-core/register --recursive --watch",
|
||||
"test:cov":
|
||||
"babel-node ./node_modules/.bin/isparta cover ./node_modules/.bin/_mocha -- --recursive",
|
||||
"prepare": "npm run build",
|
||||
"prepublishOnly": "npm run test && npm run clean && npm run build",
|
||||
"start": "cd examples && npm start",
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reduxjs/redux-devtools.git"
|
||||
},
|
||||
"keywords": ["react", "json viewer"],
|
||||
"author": "Shu Uesugi <shu@chibicode.com> (http://github.com/chibicode)",
|
||||
"contributors": [
|
||||
"Alexander Kuznetsov <alexkuz@gmail.com> (http://kuzya.org/)",
|
||||
"Dave Vedder <veddermatic@gmail.com> (http://www.eskimospy.com/)",
|
||||
"Daniele Zannotti <dzannotti@me.com> (http://www.github.com/dzannotti)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/reduxjs/redux-devtools/issues"
|
||||
},
|
||||
"homepage": "https://github.com/reduxjs/redux-devtools",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.0.1",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"eslint": "^4.10",
|
||||
"eslint-config-prettier": "^2.6.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-babel": "^4.1.2",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-node": "^5.2.1",
|
||||
"eslint-plugin-prettier": "^2.3.1",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"expect": "^21.2.1",
|
||||
"husky": "^0.14.3",
|
||||
"isparta": "^4.0.0",
|
||||
"lint-staged": "^4.3.0",
|
||||
"mocha": "^4.0.1",
|
||||
"prettier": "^1.7.4",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-test-renderer": "^16.0.0",
|
||||
"rimraf": "^2.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.0.0 || ^16.0.0",
|
||||
"react-dom": "^15.0.0 || ^16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.6.1",
|
||||
"prop-types": "^15.5.8",
|
||||
"react-base16-styling": "^0.5.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,json,css}": ["prettier --single-quote --write", "git add"]
|
||||
}
|
||||
}
|
48
packages/react-json-tree/src/ItemRange.js
vendored
Normal file
48
packages/react-json-tree/src/ItemRange.js
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JSONArrow from './JSONArrow';
|
||||
|
||||
export default class ItemRange extends React.Component {
|
||||
static propTypes = {
|
||||
styling: PropTypes.func.isRequired,
|
||||
from: PropTypes.number.isRequired,
|
||||
to: PropTypes.number.isRequired,
|
||||
renderChildNodes: PropTypes.func.isRequired,
|
||||
nodeType: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { expanded: false };
|
||||
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { styling, from, to, renderChildNodes, nodeType } = this.props;
|
||||
|
||||
return this.state.expanded ? (
|
||||
<div {...styling('itemRange', this.state.expanded)}>
|
||||
{renderChildNodes(this.props, from, to)}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
{...styling('itemRange', this.state.expanded)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<JSONArrow
|
||||
nodeType={nodeType}
|
||||
styling={styling}
|
||||
expanded={false}
|
||||
onClick={this.handleClick}
|
||||
arrowStyle="double"
|
||||
/>
|
||||
{`${from} ... ${to}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
}
|
27
packages/react-json-tree/src/JSONArrayNode.js
vendored
Normal file
27
packages/react-json-tree/src/JSONArrayNode.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JSONNestedNode from './JSONNestedNode';
|
||||
|
||||
// Returns the "n Items" string for this node,
|
||||
// generating and caching it if it hasn't been created yet.
|
||||
function createItemString(data) {
|
||||
return `${data.length} ${data.length !== 1 ? 'items' : 'item'}`;
|
||||
}
|
||||
|
||||
// Configures <JSONNestedNode> to render an Array
|
||||
const JSONArrayNode = ({ data, ...props }) => (
|
||||
<JSONNestedNode
|
||||
{...props}
|
||||
data={data}
|
||||
nodeType="Array"
|
||||
nodeTypeIndicator="[]"
|
||||
createItemString={createItemString}
|
||||
expandable={data.length > 0}
|
||||
/>
|
||||
);
|
||||
|
||||
JSONArrayNode.propTypes = {
|
||||
data: PropTypes.array
|
||||
};
|
||||
|
||||
export default JSONArrayNode;
|
27
packages/react-json-tree/src/JSONArrow.js
vendored
Normal file
27
packages/react-json-tree/src/JSONArrow.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const JSONArrow = ({ styling, arrowStyle, expanded, nodeType, onClick }) => (
|
||||
<div {...styling('arrowContainer', arrowStyle)} onClick={onClick}>
|
||||
<div {...styling(['arrow', 'arrowSign'], nodeType, expanded, arrowStyle)}>
|
||||
{'\u25B6'}
|
||||
{arrowStyle === 'double' && (
|
||||
<div {...styling(['arrowSign', 'arrowSignInner'])}>{'\u25B6'}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
JSONArrow.propTypes = {
|
||||
styling: PropTypes.func.isRequired,
|
||||
arrowStyle: PropTypes.oneOf(['single', 'double']),
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
nodeType: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
JSONArrow.defaultProps = {
|
||||
arrowStyle: 'single'
|
||||
};
|
||||
|
||||
export default JSONArrow;
|
34
packages/react-json-tree/src/JSONIterableNode.js
vendored
Normal file
34
packages/react-json-tree/src/JSONIterableNode.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import JSONNestedNode from './JSONNestedNode';
|
||||
|
||||
// Returns the "n Items" string for this node,
|
||||
// generating and caching it if it hasn't been created yet.
|
||||
function createItemString(data, limit) {
|
||||
let count = 0;
|
||||
let hasMore = false;
|
||||
if (Number.isSafeInteger(data.size)) {
|
||||
count = data.size;
|
||||
} else {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
for (const entry of data) {
|
||||
if (limit && count + 1 > limit) {
|
||||
hasMore = true;
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return `${hasMore ? '>' : ''}${count} ${count !== 1 ? 'entries' : 'entry'}`;
|
||||
}
|
||||
|
||||
// Configures <JSONNestedNode> to render an iterable
|
||||
export default function JSONIterableNode({ ...props }) {
|
||||
return (
|
||||
<JSONNestedNode
|
||||
{...props}
|
||||
nodeType="Iterable"
|
||||
nodeTypeIndicator="()"
|
||||
createItemString={createItemString}
|
||||
/>
|
||||
);
|
||||
}
|
204
packages/react-json-tree/src/JSONNestedNode.js
vendored
Normal file
204
packages/react-json-tree/src/JSONNestedNode.js
vendored
Normal file
|
@ -0,0 +1,204 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JSONArrow from './JSONArrow';
|
||||
import getCollectionEntries from './getCollectionEntries';
|
||||
import JSONNode from './JSONNode';
|
||||
import ItemRange from './ItemRange';
|
||||
|
||||
/**
|
||||
* Renders nested values (eg. objects, arrays, lists, etc.)
|
||||
*/
|
||||
|
||||
function renderChildNodes(props, from, to) {
|
||||
const {
|
||||
nodeType,
|
||||
data,
|
||||
collectionLimit,
|
||||
circularCache,
|
||||
keyPath,
|
||||
postprocessValue,
|
||||
sortObjectKeys
|
||||
} = props;
|
||||
const childNodes = [];
|
||||
|
||||
getCollectionEntries(
|
||||
nodeType,
|
||||
data,
|
||||
sortObjectKeys,
|
||||
collectionLimit,
|
||||
from,
|
||||
to
|
||||
).forEach(entry => {
|
||||
if (entry.to) {
|
||||
childNodes.push(
|
||||
<ItemRange
|
||||
{...props}
|
||||
key={`ItemRange--${entry.from}-${entry.to}`}
|
||||
from={entry.from}
|
||||
to={entry.to}
|
||||
renderChildNodes={renderChildNodes}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
const { key, value } = entry;
|
||||
const isCircular = circularCache.indexOf(value) !== -1;
|
||||
|
||||
const node = (
|
||||
<JSONNode
|
||||
{...props}
|
||||
{...{ postprocessValue, collectionLimit }}
|
||||
key={`Node--${key}`}
|
||||
keyPath={[key, ...keyPath]}
|
||||
value={postprocessValue(value)}
|
||||
circularCache={[...circularCache, value]}
|
||||
isCircular={isCircular}
|
||||
hideRoot={false}
|
||||
/>
|
||||
);
|
||||
|
||||
if (node !== false) {
|
||||
childNodes.push(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return childNodes;
|
||||
}
|
||||
|
||||
function getStateFromProps(props) {
|
||||
// calculate individual node expansion if necessary
|
||||
const expanded =
|
||||
props.shouldExpandNode && !props.isCircular
|
||||
? props.shouldExpandNode(props.keyPath, props.data, props.level)
|
||||
: false;
|
||||
return {
|
||||
expanded
|
||||
};
|
||||
}
|
||||
|
||||
export default class JSONNestedNode extends React.Component {
|
||||
static propTypes = {
|
||||
getItemString: PropTypes.func.isRequired,
|
||||
nodeTypeIndicator: PropTypes.any,
|
||||
nodeType: PropTypes.string.isRequired,
|
||||
data: PropTypes.any,
|
||||
hideRoot: PropTypes.bool.isRequired,
|
||||
createItemString: PropTypes.func.isRequired,
|
||||
styling: PropTypes.func.isRequired,
|
||||
collectionLimit: PropTypes.number,
|
||||
keyPath: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
).isRequired,
|
||||
labelRenderer: PropTypes.func.isRequired,
|
||||
shouldExpandNode: PropTypes.func,
|
||||
level: PropTypes.number.isRequired,
|
||||
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
|
||||
isCircular: PropTypes.bool,
|
||||
expandable: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
data: [],
|
||||
circularCache: [],
|
||||
level: 0,
|
||||
expandable: true
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = getStateFromProps(props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const nextState = getStateFromProps(nextProps);
|
||||
if (getStateFromProps(this.props).expanded !== nextState.expanded) {
|
||||
this.setState(nextState);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return (
|
||||
!!Object.keys(nextProps).find(
|
||||
key =>
|
||||
key !== 'circularCache' &&
|
||||
(key === 'keyPath'
|
||||
? nextProps[key].join('/') !== this.props[key].join('/')
|
||||
: nextProps[key] !== this.props[key])
|
||||
) || nextState.expanded !== this.state.expanded
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
getItemString,
|
||||
nodeTypeIndicator,
|
||||
nodeType,
|
||||
data,
|
||||
hideRoot,
|
||||
createItemString,
|
||||
styling,
|
||||
collectionLimit,
|
||||
keyPath,
|
||||
labelRenderer,
|
||||
expandable
|
||||
} = this.props;
|
||||
const { expanded } = this.state;
|
||||
const renderedChildren =
|
||||
expanded || (hideRoot && this.props.level === 0)
|
||||
? renderChildNodes({ ...this.props, level: this.props.level + 1 })
|
||||
: null;
|
||||
|
||||
const itemType = (
|
||||
<span {...styling('nestedNodeItemType', expanded)}>
|
||||
{nodeTypeIndicator}
|
||||
</span>
|
||||
);
|
||||
const renderedItemString = getItemString(
|
||||
nodeType,
|
||||
data,
|
||||
itemType,
|
||||
createItemString(data, collectionLimit)
|
||||
);
|
||||
const stylingArgs = [keyPath, nodeType, expanded, expandable];
|
||||
|
||||
return hideRoot ? (
|
||||
<li {...styling('rootNode', ...stylingArgs)}>
|
||||
<ul {...styling('rootNodeChildren', ...stylingArgs)}>
|
||||
{renderedChildren}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
<li {...styling('nestedNode', ...stylingArgs)}>
|
||||
{expandable && (
|
||||
<JSONArrow
|
||||
styling={styling}
|
||||
nodeType={nodeType}
|
||||
expanded={expanded}
|
||||
onClick={this.handleClick}
|
||||
/>
|
||||
)}
|
||||
<label
|
||||
{...styling(['label', 'nestedNodeLabel'], ...stylingArgs)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{labelRenderer(...stylingArgs)}
|
||||
</label>
|
||||
<span
|
||||
{...styling('nestedNodeItemString', ...stylingArgs)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{renderedItemString}
|
||||
</span>
|
||||
<ul {...styling('nestedNodeChildren', ...stylingArgs)}>
|
||||
{renderedChildren}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.expandable) {
|
||||
this.setState({ expanded: !this.state.expanded });
|
||||
}
|
||||
};
|
||||
}
|
106
packages/react-json-tree/src/JSONNode.js
vendored
Normal file
106
packages/react-json-tree/src/JSONNode.js
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import objType from './objType';
|
||||
import JSONObjectNode from './JSONObjectNode';
|
||||
import JSONArrayNode from './JSONArrayNode';
|
||||
import JSONIterableNode from './JSONIterableNode';
|
||||
import JSONValueNode from './JSONValueNode';
|
||||
|
||||
const JSONNode = ({
|
||||
getItemString,
|
||||
keyPath,
|
||||
labelRenderer,
|
||||
styling,
|
||||
value,
|
||||
valueRenderer,
|
||||
isCustomNode,
|
||||
...rest
|
||||
}) => {
|
||||
const nodeType = isCustomNode(value) ? 'Custom' : objType(value);
|
||||
|
||||
const simpleNodeProps = {
|
||||
getItemString,
|
||||
key: keyPath[0],
|
||||
keyPath,
|
||||
labelRenderer,
|
||||
nodeType,
|
||||
styling,
|
||||
value,
|
||||
valueRenderer
|
||||
};
|
||||
|
||||
const nestedNodeProps = {
|
||||
...rest,
|
||||
...simpleNodeProps,
|
||||
data: value,
|
||||
isCustomNode
|
||||
};
|
||||
|
||||
switch (nodeType) {
|
||||
case 'Object':
|
||||
case 'Error':
|
||||
case 'WeakMap':
|
||||
case 'WeakSet':
|
||||
return <JSONObjectNode {...nestedNodeProps} />;
|
||||
case 'Array':
|
||||
return <JSONArrayNode {...nestedNodeProps} />;
|
||||
case 'Iterable':
|
||||
case 'Map':
|
||||
case 'Set':
|
||||
return <JSONIterableNode {...nestedNodeProps} />;
|
||||
case 'String':
|
||||
return (
|
||||
<JSONValueNode {...simpleNodeProps} valueGetter={raw => `"${raw}"`} />
|
||||
);
|
||||
case 'Number':
|
||||
return <JSONValueNode {...simpleNodeProps} />;
|
||||
case 'Boolean':
|
||||
return (
|
||||
<JSONValueNode
|
||||
{...simpleNodeProps}
|
||||
valueGetter={raw => (raw ? 'true' : 'false')}
|
||||
/>
|
||||
);
|
||||
case 'Date':
|
||||
return (
|
||||
<JSONValueNode
|
||||
{...simpleNodeProps}
|
||||
valueGetter={raw => raw.toISOString()}
|
||||
/>
|
||||
);
|
||||
case 'Null':
|
||||
return <JSONValueNode {...simpleNodeProps} valueGetter={() => 'null'} />;
|
||||
case 'Undefined':
|
||||
return (
|
||||
<JSONValueNode {...simpleNodeProps} valueGetter={() => 'undefined'} />
|
||||
);
|
||||
case 'Function':
|
||||
case 'Symbol':
|
||||
return (
|
||||
<JSONValueNode
|
||||
{...simpleNodeProps}
|
||||
valueGetter={raw => raw.toString()}
|
||||
/>
|
||||
);
|
||||
case 'Custom':
|
||||
return <JSONValueNode {...simpleNodeProps} />;
|
||||
default:
|
||||
return (
|
||||
<JSONValueNode {...simpleNodeProps} valueGetter={raw => `<${nodeType}>`} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
JSONNode.propTypes = {
|
||||
getItemString: PropTypes.func.isRequired,
|
||||
keyPath: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
).isRequired,
|
||||
labelRenderer: PropTypes.func.isRequired,
|
||||
styling: PropTypes.func.isRequired,
|
||||
value: PropTypes.any,
|
||||
valueRenderer: PropTypes.func.isRequired,
|
||||
isCustomNode: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default JSONNode;
|
29
packages/react-json-tree/src/JSONObjectNode.js
vendored
Normal file
29
packages/react-json-tree/src/JSONObjectNode.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JSONNestedNode from './JSONNestedNode';
|
||||
|
||||
// Returns the "n Items" string for this node,
|
||||
// generating and caching it if it hasn't been created yet.
|
||||
function createItemString(data) {
|
||||
const len = Object.getOwnPropertyNames(data).length;
|
||||
return `${len} ${len !== 1 ? 'keys' : 'key'}`;
|
||||
}
|
||||
|
||||
// Configures <JSONNestedNode> to render an Object
|
||||
const JSONObjectNode = ({ data, ...props }) => (
|
||||
<JSONNestedNode
|
||||
{...props}
|
||||
data={data}
|
||||
nodeType="Object"
|
||||
nodeTypeIndicator={props.nodeType === 'Error' ? 'Error()' : '{}'}
|
||||
createItemString={createItemString}
|
||||
expandable={Object.getOwnPropertyNames(data).length > 0}
|
||||
/>
|
||||
);
|
||||
|
||||
JSONObjectNode.propTypes = {
|
||||
data: PropTypes.object,
|
||||
nodeType: PropTypes.string
|
||||
};
|
||||
|
||||
export default JSONObjectNode;
|
43
packages/react-json-tree/src/JSONValueNode.js
vendored
Normal file
43
packages/react-json-tree/src/JSONValueNode.js
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* Renders simple values (eg. strings, numbers, booleans, etc)
|
||||
*/
|
||||
|
||||
const JSONValueNode = ({
|
||||
nodeType,
|
||||
styling,
|
||||
labelRenderer,
|
||||
keyPath,
|
||||
valueRenderer,
|
||||
value,
|
||||
valueGetter
|
||||
}) => (
|
||||
<li {...styling('value', nodeType, keyPath)}>
|
||||
<label {...styling(['label', 'valueLabel'], nodeType, keyPath)}>
|
||||
{labelRenderer(keyPath, nodeType, false, false)}
|
||||
</label>
|
||||
<span {...styling('valueText', nodeType, keyPath)}>
|
||||
{valueRenderer(valueGetter(value), value, ...keyPath)}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
|
||||
JSONValueNode.propTypes = {
|
||||
nodeType: PropTypes.string.isRequired,
|
||||
styling: PropTypes.func.isRequired,
|
||||
labelRenderer: PropTypes.func.isRequired,
|
||||
keyPath: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
).isRequired,
|
||||
valueRenderer: PropTypes.func.isRequired,
|
||||
value: PropTypes.any,
|
||||
valueGetter: PropTypes.func
|
||||
};
|
||||
|
||||
JSONValueNode.defaultProps = {
|
||||
valueGetter: value => value
|
||||
};
|
||||
|
||||
export default JSONValueNode;
|
189
packages/react-json-tree/src/createStylingFromTheme.js
vendored
Normal file
189
packages/react-json-tree/src/createStylingFromTheme.js
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
import { createStyling } from 'react-base16-styling';
|
||||
import solarized from './themes/solarized';
|
||||
|
||||
const colorMap = theme => ({
|
||||
BACKGROUND_COLOR: theme.base00,
|
||||
TEXT_COLOR: theme.base07,
|
||||
STRING_COLOR: theme.base0B,
|
||||
DATE_COLOR: theme.base0B,
|
||||
NUMBER_COLOR: theme.base09,
|
||||
BOOLEAN_COLOR: theme.base09,
|
||||
NULL_COLOR: theme.base08,
|
||||
UNDEFINED_COLOR: theme.base08,
|
||||
FUNCTION_COLOR: theme.base08,
|
||||
SYMBOL_COLOR: theme.base08,
|
||||
LABEL_COLOR: theme.base0D,
|
||||
ARROW_COLOR: theme.base0D,
|
||||
ITEM_STRING_COLOR: theme.base0B,
|
||||
ITEM_STRING_EXPANDED_COLOR: theme.base03
|
||||
});
|
||||
|
||||
const valueColorMap = colors => ({
|
||||
String: colors.STRING_COLOR,
|
||||
Date: colors.DATE_COLOR,
|
||||
Number: colors.NUMBER_COLOR,
|
||||
Boolean: colors.BOOLEAN_COLOR,
|
||||
Null: colors.NULL_COLOR,
|
||||
Undefined: colors.UNDEFINED_COLOR,
|
||||
Function: colors.FUNCTION_COLOR,
|
||||
Symbol: colors.SYMBOL_COLOR
|
||||
});
|
||||
|
||||
const getDefaultThemeStyling = theme => {
|
||||
const colors = colorMap(theme);
|
||||
|
||||
return {
|
||||
tree: {
|
||||
border: 0,
|
||||
padding: 0,
|
||||
marginTop: '0.5em',
|
||||
marginBottom: '0.5em',
|
||||
marginLeft: '0.125em',
|
||||
marginRight: 0,
|
||||
listStyle: 'none',
|
||||
MozUserSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
backgroundColor: colors.BACKGROUND_COLOR
|
||||
},
|
||||
|
||||
value: ({ style }, nodeType, keyPath) => ({
|
||||
style: {
|
||||
...style,
|
||||
paddingTop: '0.25em',
|
||||
paddingRight: 0,
|
||||
marginLeft: '0.875em',
|
||||
WebkitUserSelect: 'text',
|
||||
MozUserSelect: 'text',
|
||||
wordWrap: 'break-word',
|
||||
paddingLeft: keyPath.length > 1 ? '2.125em' : '1.25em',
|
||||
textIndent: '-0.5em',
|
||||
wordBreak: 'break-all'
|
||||
}
|
||||
}),
|
||||
|
||||
label: {
|
||||
display: 'inline-block',
|
||||
color: colors.LABEL_COLOR
|
||||
},
|
||||
|
||||
valueLabel: {
|
||||
margin: '0 0.5em 0 0'
|
||||
},
|
||||
|
||||
valueText: ({ style }, nodeType) => ({
|
||||
style: {
|
||||
...style,
|
||||
color: valueColorMap(colors)[nodeType]
|
||||
}
|
||||
}),
|
||||
|
||||
itemRange: (styling, expanded) => ({
|
||||
style: {
|
||||
paddingTop: expanded ? 0 : '0.25em',
|
||||
cursor: 'pointer',
|
||||
color: colors.LABEL_COLOR
|
||||
}
|
||||
}),
|
||||
|
||||
arrow: ({ style }, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
marginLeft: 0,
|
||||
transition: '150ms',
|
||||
WebkitTransition: '150ms',
|
||||
MozTransition: '150ms',
|
||||
WebkitTransform: expanded ? 'rotateZ(90deg)' : 'rotateZ(0deg)',
|
||||
MozTransform: expanded ? 'rotateZ(90deg)' : 'rotateZ(0deg)',
|
||||
transform: expanded ? 'rotateZ(90deg)' : 'rotateZ(0deg)',
|
||||
transformOrigin: '45% 50%',
|
||||
WebkitTransformOrigin: '45% 50%',
|
||||
MozTransformOrigin: '45% 50%',
|
||||
position: 'relative',
|
||||
lineHeight: '1.1em',
|
||||
fontSize: '0.75em'
|
||||
}
|
||||
}),
|
||||
|
||||
arrowContainer: ({ style }, arrowStyle) => ({
|
||||
style: {
|
||||
...style,
|
||||
display: 'inline-block',
|
||||
paddingRight: '0.5em',
|
||||
paddingLeft: arrowStyle === 'double' ? '1em' : 0,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}),
|
||||
|
||||
arrowSign: {
|
||||
color: colors.ARROW_COLOR
|
||||
},
|
||||
|
||||
arrowSignInner: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: '-0.4em'
|
||||
},
|
||||
|
||||
nestedNode: ({ style }, keyPath, nodeType, expanded, expandable) => ({
|
||||
style: {
|
||||
...style,
|
||||
position: 'relative',
|
||||
paddingTop: '0.25em',
|
||||
marginLeft: keyPath.length > 1 ? '0.875em' : 0,
|
||||
paddingLeft: !expandable ? '1.125em' : 0
|
||||
}
|
||||
}),
|
||||
|
||||
rootNode: {
|
||||
padding: 0,
|
||||
margin: 0
|
||||
},
|
||||
|
||||
nestedNodeLabel: ({ style }, keyPath, nodeType, expanded, expandable) => ({
|
||||
style: {
|
||||
...style,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
WebkitUserSelect: expandable ? 'inherit' : 'text',
|
||||
MozUserSelect: expandable ? 'inherit' : 'text',
|
||||
cursor: expandable ? 'pointer' : 'default'
|
||||
}
|
||||
}),
|
||||
|
||||
nestedNodeItemString: ({ style }, keyPath, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
paddingLeft: '0.5em',
|
||||
cursor: 'default',
|
||||
color: expanded
|
||||
? colors.ITEM_STRING_EXPANDED_COLOR
|
||||
: colors.ITEM_STRING_COLOR
|
||||
}
|
||||
}),
|
||||
|
||||
nestedNodeItemType: {
|
||||
marginLeft: '0.3em',
|
||||
marginRight: '0.3em'
|
||||
},
|
||||
|
||||
nestedNodeChildren: ({ style }, nodeType, expanded) => ({
|
||||
style: {
|
||||
...style,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
listStyle: 'none',
|
||||
display: expanded ? 'block' : 'none'
|
||||
}
|
||||
}),
|
||||
|
||||
rootNodeChildren: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
listStyle: 'none'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default createStyling(getDefaultThemeStyling, {
|
||||
defaultBase16: solarized
|
||||
});
|
139
packages/react-json-tree/src/getCollectionEntries.js
vendored
Normal file
139
packages/react-json-tree/src/getCollectionEntries.js
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
function getLength(type, collection) {
|
||||
if (type === 'Object') {
|
||||
return Object.keys(collection).length;
|
||||
} else if (type === 'Array') {
|
||||
return collection.length;
|
||||
}
|
||||
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
function isIterableMap(collection) {
|
||||
return typeof collection.set === 'function';
|
||||
}
|
||||
|
||||
function getEntries(type, collection, sortObjectKeys, from = 0, to = Infinity) {
|
||||
let res;
|
||||
|
||||
if (type === 'Object') {
|
||||
let keys = Object.getOwnPropertyNames(collection);
|
||||
|
||||
if (sortObjectKeys) {
|
||||
keys.sort(sortObjectKeys === true ? undefined : sortObjectKeys);
|
||||
}
|
||||
|
||||
keys = keys.slice(from, to + 1);
|
||||
|
||||
res = {
|
||||
entries: keys.map(key => ({ key, value: collection[key] }))
|
||||
};
|
||||
} else if (type === 'Array') {
|
||||
res = {
|
||||
entries: collection
|
||||
.slice(from, to + 1)
|
||||
.map((val, idx) => ({ key: idx + from, value: val }))
|
||||
};
|
||||
} else {
|
||||
let idx = 0;
|
||||
const entries = [];
|
||||
let done = true;
|
||||
|
||||
const isMap = isIterableMap(collection);
|
||||
|
||||
for (const item of collection) {
|
||||
if (idx > to) {
|
||||
done = false;
|
||||
break;
|
||||
}
|
||||
if (from <= idx) {
|
||||
if (isMap && Array.isArray(item)) {
|
||||
if (typeof item[0] === 'string' || typeof item[0] === 'number') {
|
||||
entries.push({ key: item[0], value: item[1] });
|
||||
} else {
|
||||
entries.push({
|
||||
key: `[entry ${idx}]`,
|
||||
value: {
|
||||
'[key]': item[0],
|
||||
'[value]': item[1]
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
entries.push({ key: idx, value: item });
|
||||
}
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
res = {
|
||||
hasMore: !done,
|
||||
entries
|
||||
};
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function getRanges(from, to, limit) {
|
||||
const ranges = [];
|
||||
while (to - from > limit * limit) {
|
||||
limit = limit * limit;
|
||||
}
|
||||
for (let i = from; i <= to; i += limit) {
|
||||
ranges.push({ from: i, to: Math.min(to, i + limit - 1) });
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
export default function getCollectionEntries(
|
||||
type,
|
||||
collection,
|
||||
sortObjectKeys,
|
||||
limit,
|
||||
from = 0,
|
||||
to = Infinity
|
||||
) {
|
||||
const getEntriesBound = getEntries.bind(
|
||||
null,
|
||||
type,
|
||||
collection,
|
||||
sortObjectKeys
|
||||
);
|
||||
|
||||
if (!limit) {
|
||||
return getEntriesBound().entries;
|
||||
}
|
||||
|
||||
const isSubset = to < Infinity;
|
||||
const length = Math.min(to - from, getLength(type, collection));
|
||||
|
||||
if (type !== 'Iterable') {
|
||||
if (length <= limit || limit < 7) {
|
||||
return getEntriesBound(from, to).entries;
|
||||
}
|
||||
} else {
|
||||
if (length <= limit && !isSubset) {
|
||||
return getEntriesBound(from, to).entries;
|
||||
}
|
||||
}
|
||||
|
||||
let limitedEntries;
|
||||
if (type === 'Iterable') {
|
||||
const { hasMore, entries } = getEntriesBound(from, from + limit - 1);
|
||||
|
||||
limitedEntries = hasMore
|
||||
? [...entries, ...getRanges(from + limit, from + 2 * limit - 1, limit)]
|
||||
: entries;
|
||||
} else {
|
||||
limitedEntries = isSubset
|
||||
? getRanges(from, to, limit)
|
||||
: [
|
||||
...getEntriesBound(0, limit - 5).entries,
|
||||
...getRanges(limit - 4, length - 5, limit),
|
||||
...getEntriesBound(length - 4, length - 1).entries
|
||||
];
|
||||
}
|
||||
|
||||
return limitedEntries;
|
||||
}
|
151
packages/react-json-tree/src/index.js
vendored
Normal file
151
packages/react-json-tree/src/index.js
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
// ES6 + inline style port of JSONViewer https://bitbucket.org/davevedder/react-json-viewer/
|
||||
// all credits and original code to the author
|
||||
// Dave Vedder <veddermatic@gmail.com> http://www.eskimospy.com/
|
||||
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import JSONNode from './JSONNode';
|
||||
import createStylingFromTheme from './createStylingFromTheme';
|
||||
import { invertTheme } from 'react-base16-styling';
|
||||
|
||||
const identity = value => value;
|
||||
const expandRootNode = (keyName, data, level) => level === 0;
|
||||
const defaultItemString = (type, data, itemType, itemString) => (
|
||||
<span>
|
||||
{itemType} {itemString}
|
||||
</span>
|
||||
);
|
||||
const defaultLabelRenderer = ([label]) => <span>{label}:</span>;
|
||||
const noCustomNode = () => false;
|
||||
|
||||
function checkLegacyTheming(theme, props) {
|
||||
const deprecatedStylingMethodsMap = {
|
||||
getArrowStyle: 'arrow',
|
||||
getListStyle: 'nestedNodeChildren',
|
||||
getItemStringStyle: 'nestedNodeItemString',
|
||||
getLabelStyle: 'label',
|
||||
getValueStyle: 'valueText'
|
||||
};
|
||||
|
||||
const deprecatedStylingMethods = Object.keys(
|
||||
deprecatedStylingMethodsMap
|
||||
).filter(name => props[name]);
|
||||
|
||||
if (deprecatedStylingMethods.length > 0) {
|
||||
if (typeof theme === 'string') {
|
||||
theme = {
|
||||
extend: theme
|
||||
};
|
||||
} else {
|
||||
theme = { ...theme };
|
||||
}
|
||||
|
||||
deprecatedStylingMethods.forEach(name => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`Styling method "${name}" is deprecated, use "theme" property instead`
|
||||
);
|
||||
|
||||
theme[deprecatedStylingMethodsMap[name]] = ({ style }, ...args) => ({
|
||||
style: {
|
||||
...style,
|
||||
...props[name](...args)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
function getStateFromProps(props) {
|
||||
let theme = checkLegacyTheming(props.theme, props);
|
||||
if (props.invertTheme) {
|
||||
if (typeof theme === 'string') {
|
||||
theme = `${theme}:inverted`;
|
||||
} else if (theme && theme.extend) {
|
||||
if (typeof theme === 'string') {
|
||||
theme = { ...theme, extend: `${theme.extend}:inverted` };
|
||||
} else {
|
||||
theme = { ...theme, extend: invertTheme(theme.extend) };
|
||||
}
|
||||
} else if (theme) {
|
||||
theme = invertTheme(theme);
|
||||
}
|
||||
}
|
||||
return {
|
||||
styling: createStylingFromTheme(theme)
|
||||
};
|
||||
}
|
||||
|
||||
export default class JSONTree extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
|
||||
hideRoot: PropTypes.bool,
|
||||
theme: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
invertTheme: PropTypes.bool,
|
||||
keyPath: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||
),
|
||||
postprocessValue: PropTypes.func,
|
||||
sortObjectKeys: PropTypes.oneOfType([PropTypes.func, PropTypes.bool])
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
shouldExpandNode: expandRootNode,
|
||||
hideRoot: false,
|
||||
keyPath: ['root'],
|
||||
getItemString: defaultItemString,
|
||||
labelRenderer: defaultLabelRenderer,
|
||||
valueRenderer: identity,
|
||||
postprocessValue: identity,
|
||||
isCustomNode: noCustomNode,
|
||||
collectionLimit: 50,
|
||||
invertTheme: true
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = getStateFromProps(props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (['theme', 'invertTheme'].find(k => nextProps[k] !== this.props[k])) {
|
||||
this.setState(getStateFromProps(nextProps));
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !!Object.keys(nextProps).find(
|
||||
k =>
|
||||
k === 'keyPath'
|
||||
? nextProps[k].join('/') !== this.props[k].join('/')
|
||||
: nextProps[k] !== this.props[k]
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
data: value,
|
||||
keyPath,
|
||||
postprocessValue,
|
||||
hideRoot,
|
||||
theme, // eslint-disable-line no-unused-vars
|
||||
invertTheme: _, // eslint-disable-line no-unused-vars
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const { styling } = this.state;
|
||||
|
||||
return (
|
||||
<ul {...styling('tree')}>
|
||||
<JSONNode
|
||||
{...{ postprocessValue, hideRoot, styling, ...rest }}
|
||||
keyPath={hideRoot ? [] : keyPath}
|
||||
value={postprocessValue(value)}
|
||||
/>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
13
packages/react-json-tree/src/objType.js
vendored
Normal file
13
packages/react-json-tree/src/objType.js
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default function objType(obj) {
|
||||
const type = Object.prototype.toString.call(obj).slice(8, -1);
|
||||
if (type === 'Object' && typeof obj[Symbol.iterator] === 'function') {
|
||||
return 'Iterable';
|
||||
}
|
||||
|
||||
if (type === 'Custom' && obj.constructor !== Object && obj instanceof Object) {
|
||||
// For projects implementing objects overriding `.prototype[Symbol.toStringTag]`
|
||||
return 'Object';
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
20
packages/react-json-tree/src/themes/solarized.js
vendored
Normal file
20
packages/react-json-tree/src/themes/solarized.js
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
scheme: 'solarized',
|
||||
author: 'ethan schoonover (http://ethanschoonover.com/solarized)',
|
||||
base00: '#002b36',
|
||||
base01: '#073642',
|
||||
base02: '#586e75',
|
||||
base03: '#657b83',
|
||||
base04: '#839496',
|
||||
base05: '#93a1a1',
|
||||
base06: '#eee8d5',
|
||||
base07: '#fdf6e3',
|
||||
base08: '#dc322f',
|
||||
base09: '#cb4b16',
|
||||
base0A: '#b58900',
|
||||
base0B: '#859900',
|
||||
base0C: '#2aa198',
|
||||
base0D: '#268bd2',
|
||||
base0E: '#6c71c4',
|
||||
base0F: '#d33682'
|
||||
};
|
10
packages/react-json-tree/src/utils/hexToRgb.js
vendored
Normal file
10
packages/react-json-tree/src/utils/hexToRgb.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default function(hex) {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
}
|
||||
: null;
|
||||
}
|
23
packages/react-json-tree/test/index.spec.js
Normal file
23
packages/react-json-tree/test/index.spec.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import expect from 'expect';
|
||||
import { createRenderer } from 'react-test-renderer/shallow';
|
||||
|
||||
import JSONTree from '../src/index';
|
||||
import JSONNode from '../src/JSONNode';
|
||||
|
||||
const BASIC_DATA = { a: 1, b: 'c' };
|
||||
|
||||
function render(component) {
|
||||
const renderer = createRenderer();
|
||||
renderer.render(component);
|
||||
return renderer.getRenderOutput();
|
||||
}
|
||||
|
||||
describe('JSONTree', () => {
|
||||
it('should render basic tree', () => {
|
||||
const result = render(<JSONTree data={BASIC_DATA} />);
|
||||
|
||||
expect(result.type).toBe('ul');
|
||||
expect(result.props.children.type.name).toBe(JSONNode.name);
|
||||
});
|
||||
});
|
23
packages/react-json-tree/test/objType.spec.js
Normal file
23
packages/react-json-tree/test/objType.spec.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import expect from 'expect';
|
||||
|
||||
import objType from '../src/objType';
|
||||
|
||||
describe('objType', () => {
|
||||
it('should determine the correct type', () => {
|
||||
expect(objType({})).toBe('Object');
|
||||
expect(objType([])).toBe('Array');
|
||||
expect(objType(new Map())).toBe('Map');
|
||||
expect(objType(new WeakMap())).toBe('WeakMap');
|
||||
expect(objType(new Set())).toBe('Set');
|
||||
expect(objType(new WeakSet())).toBe('WeakSet');
|
||||
expect(objType(new Error())).toBe('Error');
|
||||
expect(objType(new Date())).toBe('Date');
|
||||
expect(objType(() => {})).toBe('Function');
|
||||
expect(objType('')).toBe('String');
|
||||
expect(objType(true)).toBe('Boolean');
|
||||
expect(objType(null)).toBe('Null');
|
||||
expect(objType(undefined)).toBe('Undefined');
|
||||
expect(objType(10)).toBe('Number');
|
||||
expect(objType(Symbol.iterator)).toBe('Symbol');
|
||||
});
|
||||
});
|
|
@ -44,7 +44,7 @@
|
|||
"eslint-plugin-flowtype": "3.2.0",
|
||||
"eslint-plugin-import": "2.14.0",
|
||||
"eslint-plugin-jsx-a11y": "6.1.1",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"jest": "^23.6.0",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"react-dom": "^15.4.0",
|
||||
|
|
Loading…
Reference in New Issue
Block a user