Fixed TreeMode rendering everything again when any options changed
This commit is contained in:
parent
6385e4b193
commit
9e0249f550
|
@ -52,17 +52,18 @@ class App extends Component {
|
|||
this.state = {
|
||||
logging: false,
|
||||
|
||||
options: {
|
||||
editorProps: {
|
||||
json,
|
||||
schema: null,
|
||||
|
||||
name: 'myObject',
|
||||
onPatch: this.handlePatch,
|
||||
onPatchText: this.handlePatchText,
|
||||
onChange: this.handleChange,
|
||||
onChangeText: this.handleChangeText,
|
||||
onChangeMode: this.handleChangeMode,
|
||||
onError: this.handleError,
|
||||
|
||||
name: 'myObject',
|
||||
mode: 'tree',
|
||||
modes: ['text', 'code', 'tree', 'form', 'view'],
|
||||
keyBindings: {
|
||||
|
@ -74,7 +75,6 @@ class App extends Component {
|
|||
escapeUnicode: true,
|
||||
history: true,
|
||||
search: true,
|
||||
|
||||
expand: expandAll
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ class App extends Component {
|
|||
<button onClick={this.handleGetJson}>Get JSON</button>
|
||||
|
||||
<label>mode:
|
||||
<select value={this.state.options.mode} onChange={this.handleSetMode}>
|
||||
<select value={this.state.editorProps.mode} onChange={this.handleSetMode}>
|
||||
<option value="text">text</option>
|
||||
<option value="code">code</option>
|
||||
<option value="tree">tree</option>
|
||||
|
@ -98,7 +98,7 @@ class App extends Component {
|
|||
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
value={this.state.options.schema !== null}
|
||||
value={this.state.editorProps.schema !== null}
|
||||
onChange={this.handleToggleJSONSchema} /> JSON Schema
|
||||
</label>
|
||||
|
||||
|
@ -107,29 +107,30 @@ class App extends Component {
|
|||
value={this.state.logging}
|
||||
onChange={this.handleToggleLogging} /> Log events
|
||||
</label>
|
||||
|
||||
<button onClick={this.forceChangeState}>Change state</button>
|
||||
</div>
|
||||
<div className="contents">
|
||||
<JSONEditor {...this.state.options} />
|
||||
<JSONEditor {...this.state.editorProps} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
handleSetJson = () => {
|
||||
this.setState({
|
||||
options: setIn(this.state.options, ['json'], largeJson)
|
||||
editorProps: setIn(this.state.editorProps, ['json'], largeJson)
|
||||
})
|
||||
}
|
||||
|
||||
handleGetJson = () => {
|
||||
// FIXME: get updating json in the state working
|
||||
const json = this.state.options.json
|
||||
const json = this.state.editorProps.json
|
||||
alert(JSON.stringify(json, null, 2))
|
||||
}
|
||||
|
||||
handleSetMode = (event) => {
|
||||
const mode = event.target.value
|
||||
this.setState({
|
||||
options: setIn(this.state.options, ['mode'], mode)
|
||||
editorProps: setIn(this.state.editorProps, ['mode'], mode)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -139,18 +140,18 @@ class App extends Component {
|
|||
}
|
||||
|
||||
handleToggleJSONSchema = (event) => {
|
||||
const s = event.target.checked ? schema : null
|
||||
const value = event.target.checked ? schema : null
|
||||
this.setState({
|
||||
options: setIn(this.state.options, ['schema'], s)
|
||||
editorProps: setIn(this.state.editorProps, ['schema'], value)
|
||||
})
|
||||
}
|
||||
|
||||
handleChange = (json) => {
|
||||
this.log('onChange json=', json)
|
||||
// FIXME: update the json in the state (after JSONEditor neatly updates it instead of generating new json every time
|
||||
// this.setState({
|
||||
// options: setIn(this.state.options, ['json'], json)
|
||||
// })
|
||||
|
||||
this.setState({
|
||||
editorProps: setIn(this.state.editorProps, ['json'], json)
|
||||
})
|
||||
}
|
||||
|
||||
handleChangeText = (text) => {
|
||||
|
@ -172,7 +173,7 @@ class App extends Component {
|
|||
this.log('switched mode from', prevMode, 'to', mode)
|
||||
|
||||
this.setState({
|
||||
options: setIn(this.state.options, ['mode'], mode)
|
||||
editorProps: setIn(this.state.editorProps, ['mode'], mode)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
font-family: sans-serif;
|
||||
font-size: 11pt;
|
||||
}
|
||||
#editor {
|
||||
#container {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
max-width : 800px;
|
||||
|
@ -141,7 +141,7 @@
|
|||
}
|
||||
|
||||
// set json
|
||||
document.getElementById('setJSON').onclick = function () {
|
||||
document.getElementById('setJSON').onclick = async function () {
|
||||
editor.set(largeJson, {
|
||||
expand: function (path) {
|
||||
return true
|
|
@ -1,4 +1,4 @@
|
|||
export const largeJson = {
|
||||
const largeJson = {
|
||||
"version": "1.0",
|
||||
"encoding": "UTF-8",
|
||||
"feed": {
|
||||
|
@ -12603,3 +12603,12 @@ export const largeJson = {
|
|||
]
|
||||
}
|
||||
}
|
||||
|
||||
// export for node.js or web
|
||||
if (typeof module !== 'undefined' && typeof exports !== 'undefined') {
|
||||
exports.largeJson = largeJson
|
||||
}
|
||||
else {
|
||||
window.largeJson = largeJson
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createElement as h, PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import initial from 'lodash/initial'
|
||||
|
||||
import ActionMenu from './menu/ActionMenu'
|
||||
|
@ -26,15 +27,28 @@ const HOVERED_CLASS_NAMES = {
|
|||
export default class JSONNode extends PureComponent {
|
||||
static URL_TITLE = 'Ctrl+Click or Ctrl+Enter to open url'
|
||||
|
||||
static propTypes = {
|
||||
prop: PropTypes.string, // in case of an object property
|
||||
index: PropTypes.number, // in case of an array item
|
||||
eson: PropTypes.oneOfType([ PropTypes.object, PropTypes.array ]).isRequired,
|
||||
|
||||
events: PropTypes.object.isRequired,
|
||||
|
||||
// options
|
||||
options: PropTypes.shape({
|
||||
name: PropTypes.string, // name of the root item
|
||||
isPropertyEditable: PropTypes.func,
|
||||
isValueEditable: PropTypes.func,
|
||||
escapeUnicode: PropTypes.bool
|
||||
})
|
||||
}
|
||||
|
||||
state = {
|
||||
menu: null, // can contain object {anchor, root}
|
||||
appendMenu: null, // can contain object {anchor, root}
|
||||
hover: false
|
||||
}
|
||||
|
||||
componentWillMount (props) {
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (hoveredNode === this) {
|
||||
hoveredNode = null
|
||||
|
@ -42,6 +56,7 @@ export default class JSONNode extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
// console.log('JSONNode.render ' + JSON.stringify(this.props.eson[META].path))
|
||||
if (this.props.eson[META].type === 'Object') {
|
||||
return this.renderJSONObject(this.props)
|
||||
}
|
||||
|
@ -64,7 +79,7 @@ export default class JSONNode extends PureComponent {
|
|||
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
||||
// this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, index, eson, options),
|
||||
this.renderReadonly(`{${props.length}}`, `Array containing ${props.length} items`),
|
||||
this.renderReadonly(`{${props.length}}`, `Object containing ${props.length} items`),
|
||||
// this.renderFloatingMenuButton(),
|
||||
this.renderError(eson[META].error)
|
||||
])
|
||||
|
@ -77,8 +92,8 @@ export default class JSONNode extends PureComponent {
|
|||
// parent: this,
|
||||
prop,
|
||||
eson: eson[prop],
|
||||
options,
|
||||
events
|
||||
events,
|
||||
options
|
||||
}))
|
||||
|
||||
childs = h('div', {key: 'childs', className: 'jsoneditor-list'}, propsChilds)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { createElement as h, Component } from 'react'
|
||||
import { createElement as h, PureComponent } from 'react'
|
||||
import isEqual from 'lodash/isEqual'
|
||||
import reverse from 'lodash/reverse'
|
||||
import initial from 'lodash/initial'
|
||||
import pick from 'lodash/pick'
|
||||
import Hammer from 'react-hammerjs'
|
||||
import jump from '../assets/jump.js/src/jump'
|
||||
import Ajv from 'ajv'
|
||||
|
@ -44,7 +45,7 @@ const MAX_HISTORY_ITEMS = 1000 // maximum number of undo/redo items to be kept
|
|||
const SEARCH_DEBOUNCE = 300 // milliseconds
|
||||
const SCROLL_DURATION = 400 // milliseconds
|
||||
|
||||
export default class TreeMode extends Component {
|
||||
export default class TreeMode extends PureComponent {
|
||||
id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
||||
|
||||
// TODO: define propTypes
|
||||
|
@ -110,6 +111,8 @@ export default class TreeMode extends Component {
|
|||
findKeyBinding: this.handleFindKeyBinding
|
||||
},
|
||||
|
||||
options: {},
|
||||
|
||||
searchResult: {
|
||||
text: '',
|
||||
matches: null,
|
||||
|
@ -155,8 +158,6 @@ export default class TreeMode extends Component {
|
|||
// Apply json
|
||||
if (nextProps.json !== this.state.json) {
|
||||
// FIXME: merge meta data from existing eson
|
||||
// FIXME: keep state as is
|
||||
|
||||
const expandCallback = this.props.expand || TreeMode.expandRoot
|
||||
const json = nextProps.json
|
||||
const eson = expand(jsonToEson(json), expandCallback)
|
||||
|
@ -165,7 +166,7 @@ export default class TreeMode extends Component {
|
|||
json,
|
||||
eson
|
||||
})
|
||||
// TODO: cleanup
|
||||
// FIXME: use patch again -> patch should keep existing meta data when for the unchanged parts of the json
|
||||
// this.patch([{
|
||||
// op: 'replace',
|
||||
// path: '',
|
||||
|
@ -188,18 +189,22 @@ export default class TreeMode extends Component {
|
|||
|
||||
// TODO: apply patchText
|
||||
// TODO: apply patch
|
||||
|
||||
// Apply JSONNode options
|
||||
const options = pick(nextProps, ['name', 'isPropertyEditable', 'isValueEditable', 'escapeUnicode'])
|
||||
if (!isEqual(options, this.state.options)) {
|
||||
this.setState({ options })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props, state } = this
|
||||
|
||||
const Node = (props.mode === 'view')
|
||||
const Node = (this.props.mode === 'view')
|
||||
? JSONNodeView
|
||||
: (props.mode === 'form')
|
||||
: (this.props.mode === 'form')
|
||||
? JSONNodeForm
|
||||
: JSONNode
|
||||
|
||||
let eson = state.eson
|
||||
let eson = this.state.eson
|
||||
|
||||
// enrich the eson with selection and JSON Schema errors
|
||||
// TODO: for optimization, we can apply errors only when the eson is changed? (a wrapper around setState or something?)
|
||||
|
@ -207,7 +212,7 @@ export default class TreeMode extends Component {
|
|||
eson = applySelection(eson, this.state.selection)
|
||||
|
||||
return h('div', {
|
||||
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
||||
className: `jsoneditor jsoneditor-mode-${this.props.mode}`,
|
||||
onKeyDown: this.handleKeyDown,
|
||||
'data-jsoneditor': 'true'
|
||||
}, [
|
||||
|
@ -231,9 +236,8 @@ export default class TreeMode extends Component {
|
|||
(eson[META].selected ? ' jsoneditor-selected' : '')},
|
||||
h(Node, {
|
||||
eson,
|
||||
events: state.events,
|
||||
options: props,
|
||||
prop: null
|
||||
events: this.state.events,
|
||||
options: this.state.options
|
||||
})
|
||||
)
|
||||
)
|
||||
|
@ -679,7 +683,7 @@ export default class TreeMode extends Component {
|
|||
// apply changes
|
||||
const result = this.patch(actions)
|
||||
|
||||
this.emitOnChange (actions, result.revert, result.data)
|
||||
this.emitOnChange (actions, result.revert, result.eson, result.json)
|
||||
}
|
||||
|
||||
handleTouchStart = (event) => {
|
||||
|
@ -781,28 +785,31 @@ export default class TreeMode extends Component {
|
|||
|
||||
/**
|
||||
* Emit an onChange event when there is a listener for it.
|
||||
* events will be fired on the next tick (after any changed state is applied)
|
||||
* @private
|
||||
* @param {ESONPatch} patch
|
||||
* @param {ESONPatch} revert
|
||||
* @param {ESON} eson
|
||||
* @param {JSON} json
|
||||
*/
|
||||
emitOnChange (patch, revert, eson) {
|
||||
if (this.props.onPatch) {
|
||||
this.props.onPatch(patch, revert)
|
||||
emitOnChange (patch, revert, eson, json) {
|
||||
const onPatch = this.props.onPatch
|
||||
if (onPatch) {
|
||||
setTimeout(() => onPatch(patch, revert))
|
||||
}
|
||||
|
||||
if (this.props.onChange || this.props.onChangeText) {
|
||||
const json = esonToJson(eson)
|
||||
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(json)
|
||||
const onChange = this.props.onChange
|
||||
const onChangeText = this.props.onChangeText
|
||||
if (onChange || onChangeText) {
|
||||
if (onChange) {
|
||||
setTimeout(() => onChange(json))
|
||||
}
|
||||
|
||||
if (this.props.onChangeText) {
|
||||
if (onChangeText) {
|
||||
const indentation = this.props.indentation || 2
|
||||
const text = JSON.stringify(json, null, indentation)
|
||||
|
||||
this.props.onChangeText(text)
|
||||
setTimeout(() => onChangeText(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -840,7 +847,7 @@ export default class TreeMode extends Component {
|
|||
historyIndex: historyIndex + 1
|
||||
})
|
||||
|
||||
this.emitOnChange (historyItem.undo, historyItem.redo, result.data)
|
||||
this.emitOnChange(historyItem.undo, historyItem.redo, result.eson, result.json)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -859,7 +866,7 @@ export default class TreeMode extends Component {
|
|||
historyIndex
|
||||
})
|
||||
|
||||
this.emitOnChange (historyItem.redo, historyItem.undo, result.data)
|
||||
this.emitOnChange(historyItem.redo, historyItem.undo, result.eson, result.json)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -882,6 +889,7 @@ export default class TreeMode extends Component {
|
|||
const expand = options.expand || (path => this.expandKeepOrExpandAll(path))
|
||||
const result = patchEson(this.state.eson, actions, expand)
|
||||
const eson = result.data
|
||||
const json = esonToJson(eson) // FIXME: apply the patch to the json too, instead of completely replacing it
|
||||
|
||||
if (this.props.history !== false) {
|
||||
// update data and store history
|
||||
|
@ -897,6 +905,7 @@ export default class TreeMode extends Component {
|
|||
// FIXME: apply search
|
||||
this.setState({
|
||||
eson,
|
||||
json,
|
||||
history,
|
||||
historyIndex: 0
|
||||
})
|
||||
|
@ -904,14 +913,19 @@ export default class TreeMode extends Component {
|
|||
else {
|
||||
// update data and don't store history
|
||||
// FIXME: apply search
|
||||
this.setState({ eson })
|
||||
this.setState({
|
||||
eson,
|
||||
json
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
patch: actions,
|
||||
revert: result.revert,
|
||||
error: result.error,
|
||||
data: eson // FIXME: shouldn't pass data here
|
||||
data: eson, // FIXME: shouldn't pass data here?
|
||||
eson, // FIXME: shouldn't pass eson here
|
||||
json // FIXME: shouldn't pass json here
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -940,8 +954,7 @@ export default class TreeMode extends Component {
|
|||
* @returns {Object | Array | string | number | boolean | null} json
|
||||
*/
|
||||
get () {
|
||||
// FIXME: keep a copy of the json file up to date with the internal eson
|
||||
return esonToJson(this.state.eson)
|
||||
return this.state.json
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -344,7 +344,7 @@ function setSearchStatus (eson, esonPointer, searchStatus) {
|
|||
/**
|
||||
* Merge selection status into the eson object, cleanup previous selection
|
||||
* @param {ESON} eson
|
||||
* @param {Selection} selection
|
||||
* @param {Selection} [selection]
|
||||
* @return {ESON} Returns updated eson object
|
||||
*/
|
||||
export function applySelection (eson, selection) {
|
||||
|
@ -462,7 +462,7 @@ export function pathsFromSelection (eson, selection) {
|
|||
* Get the contents of a list with paths
|
||||
* @param {ESON} data
|
||||
* @param {Path[]} paths
|
||||
* @return {Array.<{name: string, value: JSONType}>}
|
||||
* @return {Array.<{name: string, value: JSON}>}
|
||||
*/
|
||||
export function contentsFromPaths (data, paths) {
|
||||
return paths.map(path => {
|
||||
|
|
Loading…
Reference in New Issue