Changed internal path to Array again
This commit is contained in:
parent
667d3f32aa
commit
5416676735
|
@ -2,6 +2,7 @@ import { h, Component } from 'preact'
|
||||||
|
|
||||||
import ContextMenu from './ContextMenu'
|
import ContextMenu from './ContextMenu'
|
||||||
import { escapeHTML, unescapeHTML } from './utils/stringUtils'
|
import { escapeHTML, unescapeHTML } from './utils/stringUtils'
|
||||||
|
import { last } from './utils/arrayUtils'
|
||||||
import { getInnerText } from './utils/domUtils'
|
import { getInnerText } from './utils/domUtils'
|
||||||
import {stringConvert, valueType, isUrl} from './utils/typeUtils'
|
import {stringConvert, valueType, isUrl} from './utils/typeUtils'
|
||||||
|
|
||||||
|
@ -45,13 +46,13 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONObject ({data, path, index, options, events}) {
|
renderJSONObject ({path, data, options, events}) {
|
||||||
const childCount = data.childs.length
|
const childCount = data.childs.length
|
||||||
const contents = [
|
const contents = [
|
||||||
h('div', {class: 'jsoneditor-node jsoneditor-object'}, [
|
h('div', {class: 'jsoneditor-node jsoneditor-object'}, [
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
this.renderContextMenuButton(),
|
this.renderContextMenuButton(),
|
||||||
this.renderProperty(data, index, options),
|
this.renderProperty(path, data, options),
|
||||||
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
|
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
|
||||||
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`)
|
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`)
|
||||||
])
|
])
|
||||||
|
@ -60,7 +61,7 @@ export default class JSONNode extends Component {
|
||||||
if (data.expanded) {
|
if (data.expanded) {
|
||||||
const childs = data.childs.map(child => {
|
const childs = data.childs.map(child => {
|
||||||
return h(JSONNode, {
|
return h(JSONNode, {
|
||||||
path: path + '/' + child.prop,
|
path: path.concat(child.prop),
|
||||||
data: child,
|
data: child,
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
|
@ -73,13 +74,13 @@ export default class JSONNode extends Component {
|
||||||
return h('li', {}, contents)
|
return h('li', {}, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONArray ({data, path, index, options, events}) {
|
renderJSONArray ({path, data, options, events}) {
|
||||||
const childCount = data.childs.length
|
const childCount = data.childs.length
|
||||||
const contents = [
|
const contents = [
|
||||||
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
|
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
this.renderContextMenuButton(),
|
this.renderContextMenuButton(),
|
||||||
this.renderProperty(data, index, options),
|
this.renderProperty(path, data, options),
|
||||||
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
|
this.renderSeparator(), // TODO: remove separator for Object and Array (gives an issue in Preact)
|
||||||
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`)
|
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`)
|
||||||
])
|
])
|
||||||
|
@ -88,9 +89,8 @@ export default class JSONNode extends Component {
|
||||||
if (data.expanded) {
|
if (data.expanded) {
|
||||||
const childs = data.childs.map((child, index) => {
|
const childs = data.childs.map((child, index) => {
|
||||||
return h(JSONNode, {
|
return h(JSONNode, {
|
||||||
path: path + '/' + index,
|
path: path.concat(index),
|
||||||
data: child,
|
data: child,
|
||||||
index,
|
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
})
|
})
|
||||||
|
@ -102,12 +102,12 @@ export default class JSONNode extends Component {
|
||||||
return h('li', {}, contents)
|
return h('li', {}, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONValue ({data, index, options}) {
|
renderJSONValue ({path, data, options}) {
|
||||||
return h('li', {}, [
|
return h('li', {}, [
|
||||||
h('div', {class: 'jsoneditor-node'}, [
|
h('div', {class: 'jsoneditor-node'}, [
|
||||||
h('div', {class: 'jsoneditor-button-placeholder'}),
|
h('div', {class: 'jsoneditor-button-placeholder'}),
|
||||||
this.renderContextMenuButton(),
|
this.renderContextMenuButton(),
|
||||||
this.renderProperty(data, index, options),
|
this.renderProperty(path, data, options),
|
||||||
this.renderSeparator(),
|
this.renderSeparator(),
|
||||||
this.renderValue(data.value)
|
this.renderValue(data.value)
|
||||||
])
|
])
|
||||||
|
@ -118,21 +118,30 @@ export default class JSONNode extends Component {
|
||||||
return h('div', {class: 'jsoneditor-readonly', contentEditable: false, title}, text)
|
return h('div', {class: 'jsoneditor-readonly', contentEditable: false, title}, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderProperty (data, index, options) {
|
renderProperty (path, data, options) {
|
||||||
const isProperty = typeof data.prop === 'string'
|
if (path.length > 0) {
|
||||||
const content = isProperty
|
const content = last(path)
|
||||||
? escapeHTML(data.prop) // render the property name
|
const isIndex = typeof content === 'number'
|
||||||
: index !== undefined
|
|
||||||
? index // render the array index of the item
|
|
||||||
: JSONNode._rootName(data, options)
|
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
class: 'jsoneditor-property' + (isProperty ? '' : ' jsoneditor-readonly'),
|
class: 'jsoneditor-property' + (isIndex ? ' jsoneditor-readonly' : ''),
|
||||||
contentEditable: isProperty,
|
contentEditable: !isIndex,
|
||||||
spellCheck: 'false',
|
spellCheck: 'false',
|
||||||
onInput: this.handleChangeProperty
|
onInput: this.handleChangeProperty
|
||||||
}, content)
|
}, content)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// root node
|
||||||
|
const content = JSONNode._rootName(data, options)
|
||||||
|
|
||||||
|
return h('div', {
|
||||||
|
class: 'jsoneditor-property jsoneditor-readonly',
|
||||||
|
contentEditable: false,
|
||||||
|
spellCheck: 'false',
|
||||||
|
onInput: this.handleChangeProperty
|
||||||
|
}, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderSeparator() {
|
renderSeparator() {
|
||||||
return h('div', {class: 'jsoneditor-separator'}, ':')
|
return h('div', {class: 'jsoneditor-separator'}, ':')
|
||||||
|
@ -175,7 +184,7 @@ export default class JSONNode extends Component {
|
||||||
|
|
||||||
renderContextMenu ({anchor, root}) {
|
renderContextMenu ({anchor, root}) {
|
||||||
const path = this.props.path
|
const path = this.props.path
|
||||||
const hasParent = path !== ''
|
const hasParent = path.length > 0
|
||||||
const type = this.props.data.type
|
const type = this.props.data.type
|
||||||
const events = this.props.events
|
const events = this.props.events
|
||||||
const items = [] // array with menu items
|
const items = [] // array with menu items
|
||||||
|
@ -321,10 +330,9 @@ export default class JSONNode extends Component {
|
||||||
const newProp = unescapeHTML(getInnerText(event.target))
|
const newProp = unescapeHTML(getInnerText(event.target))
|
||||||
|
|
||||||
// remove last entry from the path to get the path of the parent object
|
// remove last entry from the path to get the path of the parent object
|
||||||
const index = this.props.path.lastIndexOf('/')
|
const parentPath = this.props.path.slice(0, this.props.path.length - 1)
|
||||||
const path = this.props.path.substr(0, index)
|
|
||||||
|
|
||||||
this.props.events.onChangeProperty(path, oldProp, newProp)
|
this.props.events.onChangeProperty(parentPath, oldProp, newProp)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeValue (event) {
|
handleChangeValue (event) {
|
||||||
|
|
78
src/Main.js
78
src/Main.js
|
@ -2,7 +2,7 @@ import { h, Component } from 'preact'
|
||||||
import * as pointer from 'json-pointer'
|
import * as pointer from 'json-pointer'
|
||||||
|
|
||||||
import { setIn, updateIn, getIn, deleteIn, cloneDeep } from './utils/objectUtils'
|
import { setIn, updateIn, getIn, deleteIn, cloneDeep } from './utils/objectUtils'
|
||||||
import { compareAsc, compareDesc } from './utils/arrayUtils'
|
import { compareAsc, compareDesc, last } from './utils/arrayUtils'
|
||||||
import { isObject } from './utils/typeUtils'
|
import { isObject } from './utils/typeUtils'
|
||||||
import bindMethods from './utils/bindMethods'
|
import bindMethods from './utils/bindMethods'
|
||||||
import JSONNode from './JSONNode'
|
import JSONNode from './JSONNode'
|
||||||
|
@ -23,7 +23,6 @@ export default class Main extends Component {
|
||||||
data: {
|
data: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
expanded: true,
|
expanded: true,
|
||||||
path: [],
|
|
||||||
childs: []
|
childs: []
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -56,7 +55,7 @@ export default class Main extends Component {
|
||||||
data: this.state.data,
|
data: this.state.data,
|
||||||
events: this.state.events,
|
events: this.state.events,
|
||||||
options: this.state.options,
|
options: this.state.options,
|
||||||
path: ''
|
path: []
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
|
@ -72,7 +71,7 @@ export default class Main extends Component {
|
||||||
console.log('handleChangeProperty', path, oldProp, newProp)
|
console.log('handleChangeProperty', path, oldProp, newProp)
|
||||||
|
|
||||||
const index = this._findIndex(path, oldProp)
|
const index = this._findIndex(path, oldProp)
|
||||||
const newPath = path + '/' + pointer.escape(newProp)
|
const newPath = path.concat(newProp)
|
||||||
|
|
||||||
this._setIn(path, ['childs', index, 'path'], newPath)
|
this._setIn(path, ['childs', index, 'path'], newPath)
|
||||||
this._setIn(path, ['childs', index, 'prop'], newProp)
|
this._setIn(path, ['childs', index, 'prop'], newProp)
|
||||||
|
@ -91,11 +90,8 @@ export default class Main extends Component {
|
||||||
|
|
||||||
// TODO: this method is quite complicated. Can we simplify it?
|
// TODO: this method is quite complicated. Can we simplify it?
|
||||||
|
|
||||||
const parsedPath = pointer.parse(path)
|
const afterProp = last(path)
|
||||||
const afterProp = parsedPath[parsedPath.length - 1]
|
const parentPath = path.slice(0, path.length - 1)
|
||||||
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
|
|
||||||
.map(entry => '/' + pointer.escape(entry)).join('')
|
|
||||||
|
|
||||||
const parent = this._getIn(parentPath)
|
const parent = this._getIn(parentPath)
|
||||||
|
|
||||||
const index = parent.type === 'array'
|
const index = parent.type === 'array'
|
||||||
|
@ -126,11 +122,8 @@ export default class Main extends Component {
|
||||||
|
|
||||||
// TODO: this method is quite complicated. Can we simplify it?
|
// TODO: this method is quite complicated. Can we simplify it?
|
||||||
|
|
||||||
const parsedPath = pointer.parse(path)
|
const prop = last(path)
|
||||||
const prop = parsedPath[parsedPath.length - 1]
|
const parentPath = path.slice(0, path.length - 1)
|
||||||
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
|
|
||||||
.map(entry => '/' + pointer.escape(entry)).join('')
|
|
||||||
|
|
||||||
const parent = this._getIn(parentPath)
|
const parent = this._getIn(parentPath)
|
||||||
|
|
||||||
const index = parent.type === 'array'
|
const index = parent.type === 'array'
|
||||||
|
@ -157,8 +150,8 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Order the childs of an array in ascending or descending order
|
||||||
* @param path
|
* @param {Array.<string | number>} path
|
||||||
* @param {'asc' | 'desc' | null} [order=null] If not provided, will toggle current ordering
|
* @param {'asc' | 'desc' | null} [order=null] If not provided, will toggle current ordering
|
||||||
*/
|
*/
|
||||||
handleSort (path, order = null) {
|
handleSort (path, order = null) {
|
||||||
|
@ -166,11 +159,6 @@ export default class Main extends Component {
|
||||||
|
|
||||||
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
||||||
|
|
||||||
const comparators = {
|
|
||||||
asc: compareAsc,
|
|
||||||
desc: compareDesc
|
|
||||||
}
|
|
||||||
|
|
||||||
let _order
|
let _order
|
||||||
if (order === 'asc' || order === 'desc') {
|
if (order === 'asc' || order === 'desc') {
|
||||||
_order = order
|
_order = order
|
||||||
|
@ -184,7 +172,7 @@ export default class Main extends Component {
|
||||||
|
|
||||||
this._updateIn(path, ['childs'], function (childs) {
|
this._updateIn(path, ['childs'], function (childs) {
|
||||||
const ordered = childs.slice(0)
|
const ordered = childs.slice(0)
|
||||||
const compare = comparators[_order] || comparators['asc']
|
const compare = _order === 'desc' ? compareDesc : compareAsc
|
||||||
|
|
||||||
ordered.sort((a, b) => compare(a.value, b.value))
|
ordered.sort((a, b) => compare(a.value, b.value))
|
||||||
|
|
||||||
|
@ -199,14 +187,14 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set ContextMenu to a json pointer, or hide the context menu by passing null
|
* Set ContextMenu to a json pointer, or hide the context menu by passing null as path
|
||||||
* @param {string | null} path
|
* @param {Array.<string | number> | null} path
|
||||||
* @param {Element} anchor
|
* @param {Element} anchor
|
||||||
* @param {Element} root
|
* @param {Element} root
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
handleShowContextMenu({path, anchor, root}) {
|
handleShowContextMenu({path, anchor, root}) {
|
||||||
let data = this.state.data
|
console.log('handleShowContextMenu', path, anchor, root)
|
||||||
|
|
||||||
// TODO: remove this cached this.state.contextMenuPath and do a brute-force sweep over the data instead?
|
// TODO: remove this cached this.state.contextMenuPath and do a brute-force sweep over the data instead?
|
||||||
// hide previous context menu (if any)
|
// hide previous context menu (if any)
|
||||||
|
@ -215,12 +203,12 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// show new menu
|
// show new menu
|
||||||
if (typeof path === 'string') {
|
if (Array.isArray(path)) {
|
||||||
this._setIn(path, ['contextMenu'], {anchor, root})
|
this._setIn(path, ['contextMenu'], {anchor, root})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
contextMenuPath: typeof path === 'string' ? path : null // store path of current menu, just to easily find it next time
|
contextMenuPath: Array.isArray(path) ? path : null // store path of current menu, just to easily find it next time
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,13 +218,13 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getIn (path, modelProps = []) {
|
_getIn (path, modelProps = []) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
const modelPath = Main._pathToModelPath(this.state.data, path)
|
||||||
|
|
||||||
return getIn(this.state.data, modelPath.concat(modelProps))
|
return getIn(this.state.data, modelPath.concat(modelProps))
|
||||||
}
|
}
|
||||||
|
|
||||||
_setIn (path, modelProps = [], value) {
|
_setIn (path, modelProps = [], value) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
const modelPath = Main._pathToModelPath(this.state.data, path)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
data: setIn(this.state.data, modelPath.concat(modelProps), value)
|
data: setIn(this.state.data, modelPath.concat(modelProps), value)
|
||||||
|
@ -244,7 +232,7 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateIn (path, modelProps = [], callback) {
|
_updateIn (path, modelProps = [], callback) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
const modelPath = Main._pathToModelPath(this.state.data, path)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
data: updateIn(this.state.data, modelPath.concat(modelProps), callback)
|
data: updateIn(this.state.data, modelPath.concat(modelProps), callback)
|
||||||
|
@ -252,7 +240,7 @@ export default class Main extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_deleteIn (path, modelProps = []) {
|
_deleteIn (path, modelProps = []) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
const modelPath = Main._pathToModelPath(this.state.data, path)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
data: deleteIn(this.state.data, modelPath.concat(modelProps))
|
data: deleteIn(this.state.data, modelPath.concat(modelProps))
|
||||||
|
@ -272,7 +260,7 @@ export default class Main extends Component {
|
||||||
// TODO: comment
|
// TODO: comment
|
||||||
set (json) {
|
set (json) {
|
||||||
this.setState({
|
this.setState({
|
||||||
data: Main._jsonToModel('', null, json, this.state.options.expand)
|
data: Main._jsonToModel([], null, json, this.state.options.expand)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,25 +269,11 @@ export default class Main extends Component {
|
||||||
*
|
*
|
||||||
* Rule: expand the root node only
|
* Rule: expand the root node only
|
||||||
*
|
*
|
||||||
* @param {string} path A JSON Pointer path
|
* @param {Array.<string | number>} path
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
static expand (path) {
|
static expand (path) {
|
||||||
return path.indexOf('/') === -1
|
return path.length === 0
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* parse json pointer into an array, and replace strings containing a number
|
|
||||||
* with a number
|
|
||||||
* @param {string} path
|
|
||||||
* @return {Array.<string | number>}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
static _parsePath (path) {
|
|
||||||
return pointer.parse(path).map(item => {
|
|
||||||
const num = Number(item)
|
|
||||||
return isNaN(num) ? item : num
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -330,10 +304,10 @@ export default class Main extends Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a JSON object into the internally used data model
|
* Convert a JSON object into the internally used data model
|
||||||
* @param {string} path
|
* @param {Array.<string | number>} path
|
||||||
* @param {string | null} prop
|
* @param {string | null} prop
|
||||||
* @param {Object | Array | string | number | boolean | null} value
|
* @param {Object | Array | string | number | boolean | null} value
|
||||||
* @param {function(path: string)} expand
|
* @param {function(path: Array.<string | number>)} expand
|
||||||
* @return {Model}
|
* @return {Model}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -343,7 +317,7 @@ export default class Main extends Component {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
prop,
|
prop,
|
||||||
childs: value.map((child, index) => Main._jsonToModel(path + '/' + index, null, child, expand))
|
childs: value.map((child, index) => Main._jsonToModel(path.concat(index), null, child, expand))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isObject(value)) {
|
else if (isObject(value)) {
|
||||||
|
@ -352,7 +326,7 @@ export default class Main extends Component {
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
prop,
|
prop,
|
||||||
childs: Object.keys(value).map(prop => {
|
childs: Object.keys(value).map(prop => {
|
||||||
return Main._jsonToModel(path + '/' + pointer.escape(prop), prop, value[prop], expand)
|
return Main._jsonToModel(path.concat(prop), prop, value[prop], expand)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue