mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-06-16 02:53:35 +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,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.0.0",
|
"babel-eslint": "^10.0.0",
|
||||||
|
"eslint-plugin-react": "7.4.0",
|
||||||
"lerna": "3.4.2"
|
"lerna": "3.4.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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/).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 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-flowtype": "3.2.0",
|
||||||
"eslint-plugin-import": "2.14.0",
|
"eslint-plugin-import": "2.14.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.1.1",
|
"eslint-plugin-jsx-a11y": "6.1.1",
|
||||||
"eslint-plugin-react": "7.11.1",
|
"eslint-plugin-react": "7.4.0",
|
||||||
"jest": "^23.6.0",
|
"jest": "^23.6.0",
|
||||||
"react-addons-test-utils": "^15.4.0",
|
"react-addons-test-utils": "^15.4.0",
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user