React API mostly working (WIP)

This commit is contained in:
jos 2016-12-22 12:08:32 +01:00
parent 98f56efc47
commit 785ab5205c
15 changed files with 541 additions and 128 deletions

View File

@ -1,3 +1,4 @@
{
"presets": ["es2015", "stage-3", "stage-2"]
"presets": ["es2015", "stage-3", "stage-2"],
"plugins": ["transform-flow-strip-types"]
}

10
.flowconfig Normal file
View File

@ -0,0 +1,10 @@
[ignore]
[include]
./src
[libs]
[options]
module.name_mapper.extension='less' -> '<PROJECT_ROOT>/src/flow/LessModule.js'
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe

View File

@ -0,0 +1,94 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>React component | JSONEditor</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.4.4/babel.min.js"></script>
</head>
<body>
<p>
This demo shows how to load JSONEditor as a React Component
</p>
<div id="root"></div>
<!-- load JSONEditor -->
<script src="../dist/jsoneditor.js"></script>
<script>
// FIXME: should use JSONEditor source code instead of bundled version (multiple versions of React can give conflicts)
// use React and ReactDOM as embedded in the library
const React = jsoneditor.React
const ReactDOM = jsoneditor.ReactDOM
</script>
<script type="text/babel">
// FIXME: should use JSONEditor source code instead of bundled version (multiple versions of React can give conflicts)
// Note that in a full React application, the editor would be loaded as:
// import {JSONEditor} from 'jsoneditor'
const JSONEditor = jsoneditor.JSONEditor
const json = {
'array': [1, 2, 3],
'boolean': true,
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd'},
'string': 'Hello World'
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
text: JSON.stringify(json, null, 2)
}
this.onChange = this.onChange.bind(this)
this.onChangeText = this.onChangeText.bind(this)
}
render () {
return <JSONEditor
mode="code"
modes={['text', 'code', 'tree', 'form', 'view']}
text={this.state.text}
onChange={this.onChange}
onChangeText={this.onChangeText}
/>
}
onChange (json) {
console.log('onChange', json)
}
onChangeText (text) {
console.log('onChangeText', text)
this.setState({ text })
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
</script>
<script>
Array.prototype.slice
.call(document.querySelectorAll('script[type="text/babel"]'))
.forEach(function(script) {
var el = document.createElement('script');
el.innerHTML = Babel.transform(script.innerText, { presets: ['es2015', 'react'] }).code;
script.parentNode.insertBefore(el, script);
});
</script>
</body>
</html>

View File

@ -47,7 +47,8 @@ var loaders = [
// create a single instance of the compiler to allow caching
var plugins = [
bannerPlugin,]
bannerPlugin
]
if (!WATCHING) {
plugins.push(new webpack.optimize.UglifyJsPlugin())
plugins.push(new webpack.DefinePlugin({

View File

@ -20,25 +20,28 @@
"scripts": {
"start": "gulp watch",
"build": "gulp",
"flow": "flow; test $? -eq 0 -o $? -eq 2",
"test": "ava test/*.test.js test/**/*.test.js --verbose"
},
"dependencies": {
"ajv": "4.8.2",
"ajv": "4.9.2",
"brace": "0.9.0",
"javascript-natural-sort": "0.7.1",
"lodash": "4.16.6",
"react": "^15.3.2",
"react-dom": "^15.3.2"
"lodash": "4.17.2",
"react": "15.4.1",
"react-dom": "15.4.1"
},
"devDependencies": {
"ava": "0.16.0",
"babel-core": "6.18.2",
"babel-loader": "6.2.7",
"ava": "0.17.0",
"babel-core": "6.20.0",
"babel-loader": "6.2.9",
"babel-plugin-transform-flow-strip-types": "6.18.0",
"babel-preset-stage-2": "6.18.0",
"babel-preset-stage-3": "6.17.0",
"browser-sync": "2.17.5",
"css-loader": "0.25.0",
"graceful-fs": "4.1.10",
"browser-sync": "2.18.2",
"css-loader": "0.26.1",
"flow-bin": "0.36.0",
"graceful-fs": "4.1.11",
"gulp": "3.9.1",
"gulp-shell": "0.5.2",
"gulp-util": "3.0.7",
@ -48,7 +51,7 @@
"mkdirp": "0.5.1",
"style-loader": "0.13.1",
"svg-url-loader": "1.1.0",
"webpack": "1.13.3"
"webpack": "1.14.0"
},
"ava": {
"require": [

View File

@ -1,3 +1,5 @@
// @flow
import { createElement as h, Component } from 'react'
import ace from '../assets/ace'
@ -12,13 +14,10 @@ import ace from '../assets/ace'
*
*/
export default class Ace extends Component {
constructor (props) {
super(props)
aceEditor = null
settingValue = false // Used to prevent Ace from emitting onChange event whilst we're setting a value programmatically
this.aceEditor = null
}
render (props, state) {
render () {
return h('div', {ref: 'container', className: 'jsoneditor-code'})
}
@ -64,35 +63,45 @@ export default class Ace extends Component {
: aceEditor
// register onchange event
this.aceEditor.on('change', this.handleChange)
// set value, the text contents for the editor
this.aceEditor.setValue(this.props.value || '', -1)
}
componentWillReceiveProps (nextProps) {
if (nextProps.value !== this.aceEditor.getValue()) {
this.aceEditor.setValue(nextProps.value, -1)
if (this.aceEditor) {
this.aceEditor.on('change', this.handleChange)
}
if (nextProps.indentation != undefined) {
// set value, the text contents for the editor
if (this.aceEditor) {
this.aceEditor.setValue(this.props.value || '', -1)
}
}
componentWillReceiveProps (nextProps: {value: string, indentation?: number}) {
if (this.aceEditor && nextProps.value !== this.aceEditor.getValue()) {
this.settingValue = true
this.aceEditor.setValue(nextProps.value, -1)
this.settingValue = false
}
if (this.aceEditor && nextProps.indentation != undefined) {
this.aceEditor.getSession().setTabSize(this.props.indentation)
}
// TODO: only resize only when needed
setTimeout(() => {
this.aceEditor.resize(false);
if (this.aceEditor) {
this.aceEditor.resize(false);
}
}, 0)
}
componentWillUnmount () {
// neatly destroy ace editor instance
this.aceEditor.destroy()
this.aceEditor = null
if (this.aceEditor) {
this.aceEditor.destroy()
this.aceEditor = null
}
}
handleChange = () => {
if (this.props && this.props.onChange) {
if (this.props && this.props.onChange && this.aceEditor && !this.settingValue) {
// TODO: pass a diff
this.props.onChange(this.aceEditor.getValue())
}

View File

@ -1,3 +1,5 @@
// @flow
import { createElement as h, Component } from 'react'
import TextMode from './TextMode'
import Ace from './Ace'
@ -28,11 +30,12 @@ import Ace from './Ace'
*
*/
export default class CodeMode extends TextMode {
constructor (props) {
constructor (props: {options: {onLoadAce: Function, indentation: number}}) {
super(props)
this.state = {
text: '{}'
text: '{}',
compiledSchema: null
}
}
@ -42,22 +45,15 @@ export default class CodeMode extends TextMode {
h('div', {key: 'contents', className: 'jsoneditor-contents'}, h(Ace, {
value: this.state.text,
onChange: this.handleChange,
onLoadAce: this.props.options.onLoadAce,
indentation: this.props.options.indentation,
ace: this.props.options.ace
onChange: this.handleChangeText,
onLoadAce: this.props.onLoadAce,
indentation: this.props.indentation,
ace: this.props.ace
})),
this.renderSchemaErrors ()
])
}
}
handleChange = (text) => {
this.setState({ text })
if (this.props.options && this.props.options.onChangeText) {
// TODO: pass a diff
this.props.options.onChangeText()
}
}
}
// TODO: define propTypes

View File

@ -0,0 +1,74 @@
// @flow
import { createElement as h, Component, PropTypes } from 'react'
import { render, unmountComponentAtNode} from 'react-dom'
import CodeMode from './CodeMode'
import TextMode from './TextMode'
import TreeMode from './TreeMode'
export default class JSONEditor extends Component {
static modeConstructors = {
code: CodeMode,
form: TreeMode,
text: TextMode,
tree: TreeMode,
view: TreeMode
}
state = {
mode: 'tree'
}
render () {
const mode = this.state.mode // We use mode from state, not from props!
const ModeConstructor = JSONEditor.modeConstructors[mode]
if (!ModeConstructor) {
// TODO: show an on screen error instead of throwing an error?
throw new Error('Unknown mode "' + mode + '". ' +
'Choose from: ' + Object.keys(this.props.modes).join(', '))
}
return h(ModeConstructor, {
...this.props,
mode,
onError: this.handleError,
onChangeMode: this.handleChangeMode
})
}
componentWillMount () {
if (this.props.mode) {
this.setState({ mode: this.props.mode })
}
}
componentWillReceiveProps (nextProps: {mode: ?string}) {
if (nextProps.mode !== this.props.mode) {
this.setState({ mode: nextProps.mode })
}
}
handleError = (err: Error) => {
if (this.props.onError) {
this.props.onError(err)
}
else {
console.error(err)
}
}
handleChangeMode = (mode: string) => {
const prevMode = this.state.mode
this.setState({ mode })
if (this.props.onChangeMode) {
this.props.onChangeMode(mode, prevMode)
}
}
}
JSONEditor.propTypes = {
mode: PropTypes.string
}

View File

@ -1,3 +1,5 @@
// @flow weak
import { createElement as h, Component } from 'react'
import ActionButton from './menu/ActionButton'
@ -15,13 +17,9 @@ let activeContextMenu = null
export default class JSONNode extends Component {
static URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url'
constructor (props) {
super(props)
this.state = {
menu: null, // context menu
appendMenu: null, // append context menu (used in placeholder of empty object/array)
}
state = {
menu: null, // context menu
appendMenu: null, // append context menu (used in placeholder of empty object/array)
}
render () {
@ -484,7 +482,7 @@ export default class JSONNode extends Component {
/**
* Singleton function to hide the currently visible context menu if any.
* @private
* @protected
*/
static hideActionMenu () {
if (activeContextMenu) {

View File

@ -1,3 +1,5 @@
// @flow weak
import { createElement as h, Component } from 'react'
import Ajv from 'ajv'
import { parseJSON } from '../utils/jsonUtils'
@ -18,7 +20,9 @@ const AJV_OPTIONS = {
* Usage:
*
* <TextMode
* options={Object}
* text={string}
* json={JSON}
* ...options
* onChange={function(text: string)}
* onChangeMode={function(mode: string)}
* onError={function(error: Error)}
@ -37,6 +41,7 @@ const AJV_OPTIONS = {
*
*/
export default class TextMode extends Component {
state: Object
constructor (props) {
super(props)
@ -47,6 +52,34 @@ export default class TextMode extends Component {
}
}
componentWillMount () {
this.applyProps(this.props, {})
}
componentWillReceiveProps (nextProps) {
this.applyProps(nextProps, this.props)
}
// TODO: create some sort of watcher structure for these props? Is there a Reactpattern for that?
applyProps (nextProps, currentProps) {
// Apply text
if (nextProps.text !== currentProps.text) {
this.setText(nextProps.text)
}
// Apply json
if (nextProps.json !== currentProps.json) {
this.set(nextProps.json)
}
// Apply JSON Schema
if (nextProps.schema !== currentProps.schema) {
this.setSchema(nextProps.schema)
}
// TODO: apply patchText
}
render () {
return h('div', {className: 'jsoneditor jsoneditor-mode-text'}, [
this.renderMenu(),
@ -88,9 +121,10 @@ export default class TextMode extends Component {
className: 'jsoneditor-vertical-menu-separator'
}),
this.props.options.modes && h(ModeButton, {
this.props.modes && h(ModeButton, {
key: 'mode',
modes: this.props.options.modes,
// TODO: simply pass all options?
modes: this.props.modes,
mode: this.props.mode,
onChangeMode: this.props.onChangeMode,
onError: this.props.onError
@ -113,9 +147,7 @@ export default class TextMode extends Component {
const allErrors = this.state.compiledSchema.errors.map(enrichSchemaError)
const limitedErrors = limitErrors(allErrors)
console.log('errors', allErrors)
return h('div', { className: 'jsoneditor-errors'},
return h('div', { key: 'errors', className: 'jsoneditor-errors'},
h('table', {},
h('tbody', {}, limitedErrors.map(TextMode.renderSchemaError))
)
@ -139,14 +171,15 @@ export default class TextMode extends Component {
/**
* Render a table row of a single JSON schema error
* @param {Error | Object | string} error
* @param {number} index
* @return {JSX.Element}
*/
static renderSchemaError (error) {
static renderSchemaError (error, index) {
const icon = h('input', {type: 'button', className: 'jsoneditor-schema-error'})
if (error && error.schema && error.schemaPath) {
// this is an ajv error message
return h('tr', {}, [
return h('tr', { key: index }, [
h('td', {key: 'icon'}, icon),
h('td', {key: 'path'}, error.dataPath),
h('td', {key: 'message'}, error.message)
@ -155,7 +188,7 @@ export default class TextMode extends Component {
else {
// any other error message
console.log('error???', error)
return h('tr', {},
return h('tr', { key: index },
h('td', {key: 'icon'}, icon),
h('td', {key: 'message', colSpan: 2}, h('code', {}, String(error)))
)
@ -169,7 +202,7 @@ export default class TextMode extends Component {
*/
setSchema (schema) {
if (schema) {
const ajv = this.props.options.ajv || Ajv && Ajv(AJV_OPTIONS)
const ajv = this.props.ajv || Ajv && Ajv(AJV_OPTIONS)
if (!ajv) {
throw new Error('Cannot validate JSON: ajv not available. ' +
@ -188,14 +221,24 @@ export default class TextMode extends Component {
}
/**
* Get the configured indentation
* @return {number}
* @protected
* Get the configured indentation. When not configured, returns the default value 2
*/
getIndentation () {
return this.props.options && this.props.options.indentation || 2
static getIndentation (props?: {indentation?: number}) : number {
return props && props.indentation || 2
}
static format (text, indentation) {
const json = parseJSON(text)
return JSON.stringify(json, null, indentation)
}
static compact (text) {
const json = parseJSON(text)
return JSON.stringify(json)
}
// TODO: move the static functions above into a separate util file
handleChange = (event) => {
// do nothing...
}
@ -206,18 +249,14 @@ export default class TextMode extends Component {
* @protected
*/
handleInput = (event) => {
this.setText(event.target.value)
if (this.props.options && this.props.options.onChangeText) {
// TODO: pass a diff
this.props.options.onChangeText()
}
this.handleChangeText(event.target.value)
}
/** @protected */
handleFormat = () => {
try {
this.format()
const formatted = TextMode.format(this.getText(), TextMode.getIndentation(this.props))
this.handleChangeText(formatted)
}
catch (err) {
this.props.onError(err)
@ -227,7 +266,8 @@ export default class TextMode extends Component {
/** @protected */
handleCompact = () => {
try {
this.compact()
const compacted = TextMode.compact(this.getText())
this.handleChangeText(compacted)
}
catch (err) {
this.props.onError(err)
@ -235,22 +275,22 @@ export default class TextMode extends Component {
}
/**
* Format the json
* Apply new text to the state, and emit an onChangeText event if there is a change
*/
format () {
const json = this.get()
const text = JSON.stringify(json, null, this.getIndentation())
this.setText(text)
handleChangeText = (text: string) => {
if (this.props.onChangeText && text !== this.state.text) {
const appliedText = this.setText(text)
this.props.onChangeText(appliedText)
}
else {
this.setText(text)
}
// TODO: also invoke a patch action
}
/**
* Compact the json
*/
compact () {
const json = this.get()
const text = JSON.stringify(json)
this.setText(text)
}
// TODO: implement method patchText
// TODO: implement callback onPatchText
/**
* Apply a JSONPatch to the current JSON document
@ -279,7 +319,7 @@ export default class TextMode extends Component {
* @param {Object | Array | string | number | boolean | null} json JSON data
*/
set (json) {
this.setText(JSON.stringify(json, null, this.getIndentation()))
this.setText(JSON.stringify(json, null, TextMode.getIndentation(this.props)))
}
/**
@ -292,14 +332,15 @@ export default class TextMode extends Component {
/**
* Set a string containing a JSON document
* @param {string} text
*/
setText (text) {
this.setState({
text: this.props.options.escapeUnicode
? escapeUnicodeChars(text)
: text
})
setText (text: string) : string {
const normalizedText = this.props.escapeUnicode
? escapeUnicodeChars(text)
: text
this.setState({ text: normalizedText })
return normalizedText
}
/**
@ -309,4 +350,7 @@ export default class TextMode extends Component {
getText () {
return this.state.text
}
}
}
// TODO: define propTypes

View File

@ -59,6 +59,43 @@ export default class TreeMode extends Component {
}
}
componentWillMount () {
this.applyProps(this.props, {})
}
componentWillReceiveProps (nextProps) {
this.applyProps(nextProps, this.props)
}
// TODO: create some sort of watcher structure for these props? Is there a Reactpattern for that?
applyProps (nextProps, currentProps) {
// Apply text
if (nextProps.text !== currentProps.text) {
this.patch([{
op: 'replace',
path: '',
value: parseJSON(nextProps.text) // FIXME: this can fail, handle error correctly
}])
}
// Apply json
if (nextProps.json !== currentProps.json) {
this.patch([{
op: 'replace',
path: '',
value: nextProps.json
}])
}
// Apply JSON Schema
if (nextProps.schema !== currentProps.schema) {
this.setSchema(nextProps.schema)
}
// TODO: apply patchText
// TODO: apply patch
}
render () {
const { props, state } = this
@ -95,7 +132,7 @@ export default class TreeMode extends Component {
h(Node, {
data,
events: state.events,
options: props.options,
options: props,
parent: null,
prop: null
})
@ -120,7 +157,7 @@ export default class TreeMode extends Component {
})
]
if (this.props.mode !== 'view' && this.props.options.history != false) {
if (this.props.mode !== 'view' && this.props.history != false) {
items = items.concat([
h('div', {key: 'history-separator', className: 'jsoneditor-vertical-menu-separator'}),
@ -141,13 +178,13 @@ export default class TreeMode extends Component {
])
}
if (this.props.options.modes ) {
if (this.props.modes ) {
items = items.concat([
h('div', {key: 'mode-separator', className: 'jsoneditor-vertical-menu-separator'}),
h(ModeButton, {
key: 'mode',
modes: this.props.options.modes,
modes: this.props.modes,
mode: this.props.mode,
onChangeMode: this.props.onChangeMode,
onError: this.props.onError
@ -155,7 +192,7 @@ export default class TreeMode extends Component {
])
}
if (this.props.options.search !== false) {
if (this.props.search !== false) {
// option search is true or undefined
items = items.concat([
h('div', {key: 'search', className: 'jsoneditor-menu-panel-right'},
@ -283,18 +320,34 @@ export default class TreeMode extends Component {
// apply changes
const result = this.patch(actions)
this.emitOnChange (actions, result.revert)
this.emitOnChange (actions, result.revert, result.data)
}
/**
* Emit an onChange event when there is a listener for it.
* @param {JSONPatch} patch
* @param {JSONPatch} revert
* @param {JSONData} data
* @private
*/
emitOnChange (patch, revert) {
if (this.props.options.onChange) {
this.props.options.onChange(patch, revert)
emitOnChange (patch, revert, data) {
if (this.props.onPatch) {
this.props.onPatch(patch, revert)
}
if (this.props.onChange || this.props.onChangeText) {
const json = dataToJson(data)
if (this.props.onChange) {
this.props.onChange(json)
}
if (this.props.onChangeText) {
const indentation = this.props.indentation || 2
const text = JSON.stringify(json, null, indentation)
this.props.onChangeText(text)
}
}
}
@ -362,7 +415,7 @@ export default class TreeMode extends Component {
const result = patchData(this.state.data, actions, expand)
const data = result.data
if (this.props.options.history != false) {
if (this.props.history != false) {
// update data and store history
const historyItem = {
redo: actions,
@ -387,19 +440,19 @@ export default class TreeMode extends Component {
return {
patch: actions,
revert: result.revert,
error: result.error
error: result.error,
data // FIXME: shouldn't pass data here
}
}
/**
* Set JSON object in editor
* @param {Object | Array | string | number | boolean | null} json JSON data
* @param {SetOptions} [options] If no expand function is provided,
* The root will be expanded and all other nodes
* will be collapsed.
*/
set (json, options = {}) {
const expand = options.expand || TreeMode.expandRoot
set (json) {
// FIXME: when both json and expand are being changed via React, this.props must be updated before set(json) is called
// TODO: document option expand
const expand = this.props.expand || TreeMode.expandRoot
this.setState({
data: jsonToData(json, expand, []),
@ -431,7 +484,7 @@ export default class TreeMode extends Component {
* @return {string} text
*/
getText () {
const indentation = this.props.options.indentation || 2
const indentation = this.props.indentation || 2
return JSON.stringify(this.get(), null, indentation)
}
@ -443,7 +496,7 @@ export default class TreeMode extends Component {
// TODO: deduplicate this function, it's also implemented in TextMode
setSchema (schema) {
if (schema) {
const ajv = this.props.options.ajv || Ajv && Ajv(AJV_OPTIONS)
const ajv = this.props.ajv || Ajv && Ajv(AJV_OPTIONS)
if (!ajv) {
throw new Error('Cannot validate JSON: ajv not available. ' +
@ -534,3 +587,5 @@ export default class TreeMode extends Component {
}
}
// TODO: describe PropTypes

View File

@ -77,7 +77,11 @@
indentation: 4,
escapeUnicode: true,
history: true,
search: true
search: true,
expand: function (path) {
return true
}
}
const editor = jsoneditor(container, options)
const json = {
@ -92,11 +96,7 @@
'unicode': 'A unicode character: \u260E',
'url': 'http://jsoneditoronline.org'
}
editor.set(json, {
expand: function (path) {
return true
}
})
editor.set(json)
const schema = {
"title": "Example Schema",

2
src/flow/LessModule.js Normal file
View File

@ -0,0 +1,2 @@
// @flow
declare export default string

View File

@ -1,5 +1,6 @@
import { createElement as h, Component } from 'react'
import { render, unmountComponentAtNode} from 'react-dom'
import React, { createElement as h, Component } from 'react'
import ReactDOM, { render, unmountComponentAtNode} from 'react-dom'
import JSONEditor from './components/JSONEditor'
import CodeMode from './components/CodeMode'
import TextMode from './components/TextMode'
import TreeMode from './components/TreeMode'
@ -45,6 +46,7 @@ function jsoneditor (container, options = {}) {
* @param {SetOptions} [options]
*/
editor.set = function (json, options = {}) {
// TODO: remove options from editor.set, move them to global options instead
editor._component.set(json, options)
}
@ -72,6 +74,30 @@ function jsoneditor (container, options = {}) {
return editor._component.getText()
}
/**
* Format the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.format = function () {
const formatted = TextMode.format(editor._component.getText(), TextMode.getIndentation(this.props))
editor._component.setText(formatted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Compact the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.compact = function () {
const compacted = TextMode.compact(editor._component.getText())
editor._component.setText(compacted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Set a JSON schema for validation of the JSON object.
* To remove the schema, call JSONEditor.setSchema(null)
@ -134,6 +160,8 @@ function jsoneditor (container, options = {}) {
* @param {'tree' | 'text'} mode
*/
editor.setMode = function (mode) {
// TODO: strongly simplify .setMode, no error handling or logic here
if (mode === editor._mode) {
// mode stays the same. do nothing
return
@ -172,8 +200,8 @@ function jsoneditor (container, options = {}) {
// create new component
component = render(
h(constructor, {
...options,
mode,
options: editor._options,
onChangeMode: handleChangeMode,
onError: handleError
}),
@ -233,4 +261,11 @@ jsoneditor.utils = {
parseJSONPointer
}
// expose React component
jsoneditor.JSONEditor = JSONEditor
// expose React itself
jsoneditor.React = React
jsoneditor.ReactDOM = ReactDOM
module.exports = jsoneditor

91
src/types.js Normal file
View File

@ -0,0 +1,91 @@
// @flow
/**
* @typedef {{
* type: 'Array',
* expanded: boolean?,
* props: Array.<{name: string, value: JSONData}>?
* }} ObjectData
*
* @typedef {{
* type: 'Object',
* expanded: boolean?,
* items: JSONData[]?
* }} ArrayData
*
* @typedef {{
* type: 'value' | 'string',
* value: *?
* }} ValueData
*
* @typedef {Array.<string>} Path
*
* @typedef {ObjectData | ArrayData | ValueData} JSONData
*
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
* @typedef {{
* patch: JSONPatch,
* revert: JSONPatch,
* error: null | Error
* }} JSONPatchResult
*
* @typedef {{
* dataPath: string,
* message: string
* }} JSONSchemaError
*
* @typedef {{
* name: string?,
* mode: 'code' | 'form' | 'text' | 'tree' | 'view'?,
* modes: string[]?,
* history: boolean?,
* indentation: number | string?,
* onChange: function (patch: JSONPatch, revert: JSONPatch)?,
* onChangeText: function ()?,
* onChangeMode: function (mode: string, prevMode: string)?,
* onError: function (err: Error)?,
* isPropertyEditable: function (Path)?
* isValueEditable: function (Path)?,
* escapeUnicode: boolean?,
* expand: function(path: Path) : boolean?,
* ajv: Object?,
* ace: Object?
* }} Options
*
* @typedef {{
* expand: function (path: Path)?
* }} PatchOptions
*
* @typedef {{
* dataPath: Path,
* property: boolean?,
* value: boolean?
* }} SearchResult
* // TODO: SearchResult.dataPath is an array, JSONSchemaError.dataPath is a string -> make this consistent
*/
type JSONType = | string | number | boolean | null | JSONObjectType | JSONArrayType;
type JSONObjectType = { [key:string]: JSON };
type JSONArrayType = Array<JSON>;
export type Path = string[]
export type SetOptions = {
expand?: (path: Path) => boolean
}
export type JSONEditorMode = {
setSchema: (schema?: Object) => void,
set: (JSON) => void,
setText: (text: string) => void,
getText: () => string
}
export type JSONPatchAction = {
op: string, // TODO: define allowed ops
path?: string,
from?: string,
value?: any
}
export type JSONPatch = JSONPatchAction[]