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:
Mihail Diordiev 2018-12-21 21:18:05 +02:00 committed by GitHub
parent a77b3ad46c
commit b80cc9e5b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2700 additions and 50 deletions

View File

@ -2,6 +2,7 @@
"private": true,
"devDependencies": {
"babel-eslint": "^10.0.0",
"eslint-plugin-react": "7.4.0",
"lerna": "3.4.2"
},
"scripts": {

View 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"]
}

View File

@ -0,0 +1,5 @@
lib
**/node_modules
**/webpack.config.js
examples/**/server.js
examples/src/App.js

View 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"
]
}

View 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.

View 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

View File

@ -0,0 +1,9 @@
{
"plugins": [
"transform-object-rest-spread",
"transform-class-properties"
],
"presets": [
"env", "react"
]
}

View 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"
]
}

View 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.

View 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

View 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>

View 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
View 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
View 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 &quot;//
type&quot;.
</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;

View File

@ -0,0 +1,5 @@
import { render } from 'react-dom';
import React from 'react';
import App from './App';
render(<App />, document.getElementById('root'));

View 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')
}]
}
};

View 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"]
}
}

View 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 });
}
}

View 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;

View 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;

View 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}
/>
);
}

View 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
View 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;

View 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;

View 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;

View 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
});

View 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
View 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
View 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;
}

View 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'
};

View 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;
}

View 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);
});
});

View 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');
});
});

View File

@ -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",

891
yarn.lock

File diff suppressed because it is too large Load Diff