New ESON model (WIP)
This commit is contained in:
parent
a9174edf16
commit
c19334894c
|
@ -2130,6 +2130,15 @@
|
||||||
"array-find-index": "1.0.2"
|
"array-find-index": "1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"d": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"es5-ext": "0.10.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
"dashdash": {
|
"dashdash": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||||
|
@ -2200,6 +2209,17 @@
|
||||||
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
|
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"deep-map": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-map/-/deep-map-1.5.0.tgz",
|
||||||
|
"integrity": "sha1-6qWVy4F4PKKADyakLgnxbn1PuJA=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"es6-weak-map": "2.0.2",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"tslib": "1.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
|
||||||
|
@ -2547,6 +2567,49 @@
|
||||||
"is-arrayish": "0.2.1"
|
"is-arrayish": "0.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es5-ext": {
|
||||||
|
"version": "0.10.37",
|
||||||
|
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz",
|
||||||
|
"integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"es6-iterator": "2.0.3",
|
||||||
|
"es6-symbol": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es6-iterator": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||||
|
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"d": "1.0.0",
|
||||||
|
"es5-ext": "0.10.37",
|
||||||
|
"es6-symbol": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es6-symbol": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
|
||||||
|
"integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"d": "1.0.0",
|
||||||
|
"es5-ext": "0.10.37"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es6-weak-map": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
|
||||||
|
"integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"d": "1.0.0",
|
||||||
|
"es5-ext": "0.10.37",
|
||||||
|
"es6-iterator": "2.0.3",
|
||||||
|
"es6-symbol": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"escape-html": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
@ -7372,6 +7435,12 @@
|
||||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"tslib": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"tty-browserify": {
|
"tty-browserify": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"babel-preset-stage-3": "6.17.0",
|
"babel-preset-stage-3": "6.17.0",
|
||||||
"browser-sync": "2.18.6",
|
"browser-sync": "2.18.6",
|
||||||
"css-loader": "0.26.1",
|
"css-loader": "0.26.1",
|
||||||
|
"deep-map": "1.5.0",
|
||||||
"flow-bin": "0.37.4",
|
"flow-bin": "0.37.4",
|
||||||
"graceful-fs": "4.1.11",
|
"graceful-fs": "4.1.11",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
|
|
|
@ -8,7 +8,7 @@ import FloatingMenu from './menu/FloatingMenu'
|
||||||
import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
|
import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
|
||||||
import { getInnerText, insideRect, findParentWithAttribute } from '../utils/domUtils'
|
import { getInnerText, insideRect, findParentWithAttribute } from '../utils/domUtils'
|
||||||
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
||||||
import { compileJSONPointer, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
|
import { compileJSONPointer, mapEsonArray, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
|
||||||
|
|
||||||
import type { ESONObjectProperty, ESON, SearchResultStatus, Path } from '../types'
|
import type { ESONObjectProperty, ESON, SearchResultStatus, Path } from '../types'
|
||||||
|
|
||||||
|
@ -36,12 +36,6 @@ export default class JSONNode extends PureComponent {
|
||||||
hover: false
|
hover: false
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.path = this.getPath(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount (props) {
|
componentWillMount (props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,26 +45,20 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
this.path = this.getPath(nextProps)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { props } = this
|
if (this.props.eson._meta.type === 'Object') {
|
||||||
|
return this.renderJSONObject(this.props)
|
||||||
if (props.data.type === 'Array') {
|
|
||||||
return this.renderJSONArray(props)
|
|
||||||
}
|
}
|
||||||
else if (props.data.type === 'Object') {
|
else if (this.props.eson._meta.type === 'Array') {
|
||||||
return this.renderJSONObject(props)
|
return this.renderJSONArray(this.props)
|
||||||
}
|
}
|
||||||
else {
|
else { // no Object or Array
|
||||||
return this.renderJSONValue(props)
|
return this.renderJSONValue(this.props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONObject ({prop, index, data, options, events}) {
|
renderJSONObject ({prop, index, eson, options, events}) {
|
||||||
const childCount = data.props.length
|
const keys = eson._meta.keys
|
||||||
const node = h('div', {
|
const node = h('div', {
|
||||||
key: 'node',
|
key: 'node',
|
||||||
onKeyDown: this.handleKeyDown,
|
onKeyDown: this.handleKeyDown,
|
||||||
|
@ -79,20 +67,20 @@ export default class JSONNode extends PureComponent {
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
||||||
// this.renderActionMenuButton(),
|
// this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, index, data, options),
|
this.renderProperty(prop, index, eson, options),
|
||||||
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`),
|
this.renderReadonly(`{${keys.length}}`, `Array containing ${keys.length} items`),
|
||||||
// this.renderFloatingMenuButton(),
|
// this.renderFloatingMenuButton(),
|
||||||
this.renderError(data.error)
|
this.renderError(eson._meta.error) // FIXME: render error
|
||||||
])
|
])
|
||||||
|
|
||||||
let childs
|
let childs
|
||||||
if (data.expanded) {
|
if (eson._meta.expanded) {
|
||||||
if (data.props.length > 0) {
|
if (keys.length > 0) {
|
||||||
const props = data.props.map(prop => h(this.constructor, {
|
const props = keys.map(key => h(this.constructor, {
|
||||||
key: prop.id,
|
key: eson[key]._meta.id,
|
||||||
parent: this,
|
// parent: this,
|
||||||
prop,
|
prop: key,
|
||||||
data: prop.value,
|
eson: eson[key],
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
}))
|
}))
|
||||||
|
@ -106,7 +94,7 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const floatingMenu = (data.selected === SELECTED_END)
|
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||||
? this.renderFloatingMenu([
|
? this.renderFloatingMenu([
|
||||||
{type: 'sort'},
|
{type: 'sort'},
|
||||||
{type: 'duplicate'},
|
{type: 'duplicate'},
|
||||||
|
@ -120,16 +108,14 @@ export default class JSONNode extends PureComponent {
|
||||||
const insertArea = this.renderInsertBeforeArea()
|
const insertArea = this.renderInsertBeforeArea()
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
'data-path': compileJSONPointer(this.path),
|
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||||
onMouseOver: this.handleMouseOver,
|
onMouseOver: this.handleMouseOver,
|
||||||
onMouseLeave: this.handleMouseLeave
|
onMouseLeave: this.handleMouseLeave
|
||||||
}, [node, floatingMenu, insertArea, childs])
|
}, [node, floatingMenu, insertArea, childs])
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: extract a function renderChilds shared by both renderJSONObject and renderJSONArray (rename .props and .items to .childs?)
|
renderJSONArray ({prop, index, eson, options, events}) {
|
||||||
renderJSONArray ({prop, index, data, options, events}) {
|
|
||||||
const childCount = data.items.length
|
|
||||||
const node = h('div', {
|
const node = h('div', {
|
||||||
key: 'node',
|
key: 'node',
|
||||||
onKeyDown: this.handleKeyDown,
|
onKeyDown: this.handleKeyDown,
|
||||||
|
@ -138,20 +124,20 @@ export default class JSONNode extends PureComponent {
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
||||||
// this.renderActionMenuButton(),
|
// this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, index, data, options),
|
this.renderProperty(prop, index, eson, options),
|
||||||
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`),
|
this.renderReadonly(`[${eson._meta.length}]`, `Array containing ${eson._meta.length} items`),
|
||||||
// this.renderFloatingMenuButton(),
|
// this.renderFloatingMenuButton(),
|
||||||
this.renderError(data.error)
|
this.renderError(eson._meta.error)
|
||||||
])
|
])
|
||||||
|
|
||||||
let childs
|
let childs
|
||||||
if (data.expanded) {
|
if (eson._meta.expanded) {
|
||||||
if (data.items.length > 0) {
|
if (eson._meta.length > 0) {
|
||||||
const items = data.items.map((item, index) => h(this.constructor, {
|
const items = mapEsonArray(eson, (item, index) => h(this.constructor, {
|
||||||
key : item.id,
|
key : item._meta.id,
|
||||||
parent: this,
|
// parent: this,
|
||||||
index,
|
index,
|
||||||
data: item.value,
|
eson: item,
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
}))
|
}))
|
||||||
|
@ -165,7 +151,7 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const floatingMenu = (data.selected === SELECTED_END)
|
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||||
? this.renderFloatingMenu([
|
? this.renderFloatingMenu([
|
||||||
{type: 'sort'},
|
{type: 'sort'},
|
||||||
{type: 'duplicate'},
|
{type: 'duplicate'},
|
||||||
|
@ -179,14 +165,14 @@ export default class JSONNode extends PureComponent {
|
||||||
const insertArea = this.renderInsertBeforeArea()
|
const insertArea = this.renderInsertBeforeArea()
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
'data-path': compileJSONPointer(this.path),
|
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||||
onMouseOver: this.handleMouseOver,
|
onMouseOver: this.handleMouseOver,
|
||||||
onMouseLeave: this.handleMouseLeave
|
onMouseLeave: this.handleMouseLeave
|
||||||
}, [node, floatingMenu, insertArea, childs])
|
}, [node, floatingMenu, insertArea, childs])
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONValue ({prop, index, data, options}) {
|
renderJSONValue ({prop, index, eson, options}) {
|
||||||
const node = h('div', {
|
const node = h('div', {
|
||||||
key: 'node',
|
key: 'node',
|
||||||
onKeyDown: this.handleKeyDown,
|
onKeyDown: this.handleKeyDown,
|
||||||
|
@ -195,14 +181,14 @@ export default class JSONNode extends PureComponent {
|
||||||
this.renderPlaceholder(),
|
this.renderPlaceholder(),
|
||||||
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
// this.renderActionMenu('update', this.state.menu, this.handleCloseActionMenu),
|
||||||
// this.renderActionMenuButton(),
|
// this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, index, data, options),
|
this.renderProperty(prop, index, eson, options),
|
||||||
this.renderSeparator(),
|
this.renderSeparator(),
|
||||||
this.renderValue(data.value, data.searchResult, options),
|
this.renderValue(eson._meta.value, eson._meta.searchResult, options),
|
||||||
// this.renderFloatingMenuButton(),
|
// this.renderFloatingMenuButton(),
|
||||||
this.renderError(data.error)
|
this.renderError(eson._meta.error)
|
||||||
])
|
])
|
||||||
|
|
||||||
const floatingMenu = (data.selected === SELECTED_END)
|
const floatingMenu = (eson._meta.selected === SELECTED_END)
|
||||||
? this.renderFloatingMenu([
|
? this.renderFloatingMenu([
|
||||||
// {text: 'String', onClick: this.props.events.onChangeType, type: 'checkbox', checked: false},
|
// {text: 'String', onClick: this.props.events.onChangeType, type: 'checkbox', checked: false},
|
||||||
{type: 'duplicate'},
|
{type: 'duplicate'},
|
||||||
|
@ -216,15 +202,15 @@ export default class JSONNode extends PureComponent {
|
||||||
const insertArea = this.renderInsertBeforeArea()
|
const insertArea = this.renderInsertBeforeArea()
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
'data-path': compileJSONPointer(this.path),
|
'data-path': compileJSONPointer(this.props.eson._meta.path),
|
||||||
className: this.getContainerClassName(data.selected, this.state.hover),
|
className: this.getContainerClassName(eson._meta.selected, this.state.hover),
|
||||||
onMouseOver: this.handleMouseOver,
|
onMouseOver: this.handleMouseOver,
|
||||||
onMouseLeave: this.handleMouseLeave
|
onMouseLeave: this.handleMouseLeave
|
||||||
}, [node, floatingMenu, insertArea])
|
}, [node, floatingMenu, insertArea])
|
||||||
}
|
}
|
||||||
|
|
||||||
renderInsertBeforeArea () {
|
renderInsertBeforeArea () {
|
||||||
const floatingMenu = (this.props.data.selected === SELECTED_BEFORE)
|
const floatingMenu = (this.props.eson._meta.selected === SELECTED_BEFORE)
|
||||||
? this.renderFloatingMenu([
|
? this.renderFloatingMenu([
|
||||||
{type: 'insertStructure'},
|
{type: 'insertStructure'},
|
||||||
{type: 'insertValue'},
|
{type: 'insertValue'},
|
||||||
|
@ -248,7 +234,7 @@ export default class JSONNode extends PureComponent {
|
||||||
*/
|
*/
|
||||||
renderAppend (text) {
|
renderAppend (text) {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
'data-path': compileJSONPointer(this.path) + '/-',
|
'data-path': compileJSONPointer(this.props.eson._meta.path) + '/-',
|
||||||
className: 'jsoneditor-node',
|
className: 'jsoneditor-node',
|
||||||
onKeyDown: this.handleKeyDownAppend
|
onKeyDown: this.handleKeyDownAppend
|
||||||
}, [
|
}, [
|
||||||
|
@ -268,12 +254,12 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: simplify the method renderProperty
|
// TODO: simplify the method renderProperty
|
||||||
renderProperty (prop?: ESONObjectProperty, index?: number, data: ESON, options: {escapeUnicode: boolean, isPropertyEditable: (Path) => boolean}) {
|
renderProperty (prop?: ESONObjectProperty, index?: number, eson: ESON, options: {escapeUnicode: boolean, isPropertyEditable: (Path) => boolean}) {
|
||||||
const isIndex = typeof index === 'number'
|
const isIndex = typeof index === 'number'
|
||||||
|
|
||||||
if (!prop && !isIndex) {
|
if (!prop && !isIndex) {
|
||||||
// root node
|
// root node
|
||||||
const rootName = JSONNode.getRootName(data, options)
|
const rootName = JSONNode.getRootName(eson, options)
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
key: 'property',
|
key: 'property',
|
||||||
|
@ -283,11 +269,11 @@ export default class JSONNode extends PureComponent {
|
||||||
}, rootName)
|
}, rootName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.path))
|
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.props.eson._meta.path))
|
||||||
|
|
||||||
const emptyClassName = (prop && prop.name.length === 0) ? ' jsoneditor-empty' : ''
|
const emptyClassName = (prop != null && prop.length === 0) ? ' jsoneditor-empty' : ''
|
||||||
const searchClassName = prop ? JSONNode.getSearchResultClass(prop.searchResult) : ''
|
const searchClassName = prop != null ? JSONNode.getSearchResultClass(prop.searchResult) : ''
|
||||||
const escapedPropName = prop ? escapeHTML(prop.name, options.escapeUnicode) : null
|
const escapedPropName = prop != null ? escapeHTML(prop, options.escapeUnicode) : null
|
||||||
|
|
||||||
if (editable) {
|
if (editable) {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
|
@ -317,7 +303,7 @@ export default class JSONNode extends PureComponent {
|
||||||
const itsAnUrl = isUrl(value)
|
const itsAnUrl = isUrl(value)
|
||||||
const isEmpty = escapedValue.length === 0
|
const isEmpty = escapedValue.length === 0
|
||||||
|
|
||||||
const editable = !options.isValueEditable || options.isValueEditable(this.path)
|
const editable = !options.isValueEditable || options.isValueEditable(this.props.eson._meta.path)
|
||||||
if (editable) {
|
if (editable) {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
key: 'value',
|
key: 'value',
|
||||||
|
@ -409,7 +395,7 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
||||||
JSONNode.getSearchResultClass(this.props.data.searchResult)
|
JSONNode.getSearchResultClass(this.props.eson._meta.searchResult)
|
||||||
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
||||||
|
|
||||||
// remove all classNames from childs (needed for IE and Edge)
|
// remove all classNames from childs (needed for IE and Edge)
|
||||||
|
@ -462,7 +448,7 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderExpandButton () {
|
renderExpandButton () {
|
||||||
const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}`
|
const className = `jsoneditor-button jsoneditor-${this.props.eson._meta.expanded ? 'expanded' : 'collapsed'}`
|
||||||
|
|
||||||
return h('div', {key: 'expand', className: 'jsoneditor-button-container'},
|
return h('div', {key: 'expand', className: 'jsoneditor-button-container'},
|
||||||
h('button', {
|
h('button', {
|
||||||
|
@ -483,9 +469,9 @@ export default class JSONNode extends PureComponent {
|
||||||
|
|
||||||
return h(ActionMenu, {
|
return h(ActionMenu, {
|
||||||
key: 'menu',
|
key: 'menu',
|
||||||
path: this.path,
|
path: this.props.eson._meta.path,
|
||||||
events: this.props.events,
|
events: this.props.events,
|
||||||
type: this.props.data.type,
|
type: this.props.eson._meta.type, // TODO: fix type
|
||||||
|
|
||||||
menuType,
|
menuType,
|
||||||
open: true,
|
open: true,
|
||||||
|
@ -527,7 +513,7 @@ export default class JSONNode extends PureComponent {
|
||||||
renderFloatingMenu (items) {
|
renderFloatingMenu (items) {
|
||||||
return h(FloatingMenu, {
|
return h(FloatingMenu, {
|
||||||
key: 'floating-menu',
|
key: 'floating-menu',
|
||||||
path: this.path,
|
path: this.props.eson._meta.path,
|
||||||
events: this.props.events,
|
events: this.props.events,
|
||||||
items
|
items
|
||||||
})
|
})
|
||||||
|
@ -609,17 +595,15 @@ export default class JSONNode extends PureComponent {
|
||||||
this.setState({ appendMenu: null })
|
this.setState({ appendMenu: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
static getRootName (data, options) {
|
static getRootName (eson, options) {
|
||||||
return typeof options.name === 'string'
|
return typeof options.name === 'string'
|
||||||
? options.name
|
? options.name
|
||||||
: (data.type === 'Object' || data.type === 'Array')
|
: valueType(eson)
|
||||||
? data.type
|
|
||||||
: valueType(data.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
handleChangeProperty = (event) => {
|
handleChangeProperty = (event) => {
|
||||||
const parentPath = initial(this.path)
|
const parentPath = initial(this.props.eson._meta.path)
|
||||||
const oldProp = this.props.prop.name
|
const oldProp = this.props.prop.name
|
||||||
const newProp = unescapeHTML(getInnerText(event.target))
|
const newProp = unescapeHTML(getInnerText(event.target))
|
||||||
|
|
||||||
|
@ -632,8 +616,8 @@ export default class JSONNode extends PureComponent {
|
||||||
handleChangeValue = (event) => {
|
handleChangeValue = (event) => {
|
||||||
const value = this.getValueFromEvent(event)
|
const value = this.getValueFromEvent(event)
|
||||||
|
|
||||||
if (value !== this.props.data.value) {
|
if (value !== this.props.eson._meta.value) {
|
||||||
this.props.events.onChangeValue(this.path, value)
|
this.props.events.onChangeValue(this.props.eson._meta.path, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,24 +634,24 @@ export default class JSONNode extends PureComponent {
|
||||||
|
|
||||||
if (keyBinding === 'duplicate') {
|
if (keyBinding === 'duplicate') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.props.events.onDuplicate(this.path)
|
this.props.events.onDuplicate(this.props.eson._meta.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyBinding === 'insert') {
|
if (keyBinding === 'insert') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.props.events.onInsert(this.path, 'value')
|
this.props.events.onInsert(this.props.eson._meta.path, 'value')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyBinding === 'remove') {
|
if (keyBinding === 'remove') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.props.events.onRemove(this.path)
|
this.props.events.onRemove(this.props.eson._meta.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyBinding === 'expand') {
|
if (keyBinding === 'expand') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const recurse = false
|
const recurse = false
|
||||||
const expanded = !this.props.data.expanded
|
const expanded = !this.props.eson._meta.expanded
|
||||||
this.props.events.onExpand(this.path, expanded, recurse)
|
this.props.events.onExpand(this.props.eson._meta.path, expanded, recurse)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyBinding === 'actionMenu') {
|
if (keyBinding === 'actionMenu') {
|
||||||
|
@ -682,7 +666,7 @@ export default class JSONNode extends PureComponent {
|
||||||
|
|
||||||
if (keyBinding === 'insert') {
|
if (keyBinding === 'insert') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
this.props.events.onAppend(this.path, 'value')
|
this.props.events.onAppend(this.props.eson._meta.path, 'value')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyBinding === 'actionMenu') {
|
if (keyBinding === 'actionMenu') {
|
||||||
|
@ -703,9 +687,10 @@ export default class JSONNode extends PureComponent {
|
||||||
/** @private */
|
/** @private */
|
||||||
handleExpand = (event) => {
|
handleExpand = (event) => {
|
||||||
const recurse = event.ctrlKey
|
const recurse = event.ctrlKey
|
||||||
const expanded = !this.props.data.expanded
|
const path = this.props.eson._meta.path
|
||||||
|
const expanded = !this.props.eson._meta.expanded
|
||||||
|
|
||||||
this.props.events.onExpand(this.path, expanded, recurse)
|
this.props.events.onExpand(path, expanded, recurse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -724,17 +709,6 @@ export default class JSONNode extends PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this construction with passing parents to determine the path is not very nice. Move determining of the path to the ESON model. We cannot generate the path whilst rendering, that defeats the efficiency of PureComponent
|
|
||||||
getPath (props = this.props) {
|
|
||||||
const parentPath = props.parent ? props.parent.path : []
|
|
||||||
|
|
||||||
return props.prop
|
|
||||||
? parentPath.concat(props.prop.name)
|
|
||||||
: typeof props.index !== 'undefined'
|
|
||||||
? parentPath.concat(props.index)
|
|
||||||
: parentPath
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the target of an event, and convert it to it's type
|
* Get the value of the target of an event, and convert it to it's type
|
||||||
* @param event
|
* @param event
|
||||||
|
@ -743,7 +717,7 @@ export default class JSONNode extends PureComponent {
|
||||||
*/
|
*/
|
||||||
getValueFromEvent (event) {
|
getValueFromEvent (event) {
|
||||||
const stringValue = unescapeHTML(getInnerText(event.target))
|
const stringValue = unescapeHTML(getInnerText(event.target))
|
||||||
return this.props.data.type === 'string'
|
return this.props.eson._meta.type === 'string'
|
||||||
? stringValue
|
? stringValue
|
||||||
: stringConvert(stringValue)
|
: stringConvert(stringValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ import Hammer from 'react-hammerjs'
|
||||||
import jump from '../assets/jump.js/src/jump'
|
import jump from '../assets/jump.js/src/jump'
|
||||||
import Ajv from 'ajv'
|
import Ajv from 'ajv'
|
||||||
|
|
||||||
import { setIn } from '../utils/immutabilityHelpers'
|
import { setIn, updateIn } from '../utils/immutabilityHelpers'
|
||||||
import { parseJSON } from '../utils/jsonUtils'
|
import { parseJSON } from '../utils/jsonUtils'
|
||||||
import { enrichSchemaError } from '../utils/schemaUtils'
|
import { enrichSchemaError } from '../utils/schemaUtils'
|
||||||
import {
|
import {
|
||||||
jsonToEson, esonToJson, getInEson, updateInEson, pathExists,
|
toEson2, jsonToEson, esonToJson, getInEson, updateInEson, pathExists,
|
||||||
expand, expandPath, addErrors,
|
expand, expandPath, addErrors,
|
||||||
search, applySearchResults, nextSearchResult, previousSearchResult,
|
search, applySearchResults, nextSearchResult, previousSearchResult,
|
||||||
applySelection, pathsFromSelection, contentsFromPaths,
|
applySelection, pathsFromSelection, contentsFromPaths,
|
||||||
|
@ -20,7 +20,7 @@ import {
|
||||||
} from '../eson'
|
} from '../eson'
|
||||||
import { patchEson } from '../patchEson'
|
import { patchEson } from '../patchEson'
|
||||||
import {
|
import {
|
||||||
duplicate, insert, insertBefore, append, remove, removeAll, replace,
|
duplicate, insertBefore, append, remove, removeAll, replace,
|
||||||
createEntry, changeType, changeValue, changeProperty, sort
|
createEntry, changeType, changeValue, changeProperty, sort
|
||||||
} from '../actions'
|
} from '../actions'
|
||||||
import JSONNode from './JSONNode'
|
import JSONNode from './JSONNode'
|
||||||
|
@ -56,7 +56,9 @@ export default class TreeMode extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
const data = jsonToEson(this.props.data || {}, TreeMode.expandAll, [])
|
const json = this.props.json || {}
|
||||||
|
const expandCallback = this.props.expand || TreeMode.expandRoot
|
||||||
|
const eson = expand(toEson2(json), expandCallback)
|
||||||
|
|
||||||
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
|
||||||
|
|
||||||
|
@ -79,9 +81,10 @@ export default class TreeMode extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
data,
|
json,
|
||||||
|
eson,
|
||||||
|
|
||||||
history: [data],
|
history: [eson],
|
||||||
historyIndex: 0,
|
historyIndex: 0,
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
|
@ -148,11 +151,17 @@ export default class TreeMode extends Component {
|
||||||
|
|
||||||
// Apply json
|
// Apply json
|
||||||
if (nextProps.json !== currentProps.json) {
|
if (nextProps.json !== currentProps.json) {
|
||||||
this.patch([{
|
// FIXME: merge _meta from existing eson
|
||||||
op: 'replace',
|
this.setState({
|
||||||
path: '',
|
json: nextProps.json,
|
||||||
value: nextProps.json
|
eson: toEson2(nextProps.json) // FIXME: how to handle expand?
|
||||||
}])
|
})
|
||||||
|
// TODO: cleanup
|
||||||
|
// this.patch([{
|
||||||
|
// op: 'replace',
|
||||||
|
// path: '',
|
||||||
|
// value: nextProps.json
|
||||||
|
// }])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply JSON Schema
|
// Apply JSON Schema
|
||||||
|
@ -172,7 +181,7 @@ export default class TreeMode extends Component {
|
||||||
// TODO: apply patch
|
// TODO: apply patch
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { props, state } = this
|
const { props, state } = this
|
||||||
|
|
||||||
const Node = (props.mode === 'view')
|
const Node = (props.mode === 'view')
|
||||||
|
@ -182,21 +191,23 @@ export default class TreeMode extends Component {
|
||||||
: JSONNode
|
: JSONNode
|
||||||
|
|
||||||
// enrich the data with JSON Schema errors
|
// enrich the data with JSON Schema errors
|
||||||
let data = state.data
|
let eson = state.eson
|
||||||
const errors = this.getErrors()
|
// TODO: reimplement errors
|
||||||
if (errors.length) {
|
// const errors = this.getErrors()
|
||||||
data = addErrors(data, this.getErrors())
|
// if (errors.length) {
|
||||||
}
|
// data = addErrors(data, this.getErrors())
|
||||||
|
// }
|
||||||
|
|
||||||
// enrich the data with search results
|
// enrich the data with search results
|
||||||
// TODO: performance improvements in search would be nice though it's acceptable right now
|
// TODO: reimplement search and selection
|
||||||
const searchResults = this.state.search.text ? search(data, this.state.search.text) : null
|
const searchResults = []
|
||||||
if (searchResults) {
|
// const searchResults = this.state.search.text ? search(data, this.state.search.text) : null
|
||||||
data = applySearchResults(data, searchResults, this.state.search.active)
|
// if (searchResults) {
|
||||||
}
|
// data = applySearchResults(data, searchResults, this.state.search.active)
|
||||||
if (this.state.selection) {
|
// }
|
||||||
data = applySelection(data, this.state.selection)
|
// if (this.state.selection) {
|
||||||
}
|
// data = applySelection(data, this.state.selection)
|
||||||
|
// }
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
||||||
|
@ -220,12 +231,11 @@ export default class TreeMode extends Component {
|
||||||
onMouseDown: this.handleTouchStart,
|
onMouseDown: this.handleTouchStart,
|
||||||
onTouchStart: this.handleTouchStart,
|
onTouchStart: this.handleTouchStart,
|
||||||
className: 'jsoneditor-list jsoneditor-root' +
|
className: 'jsoneditor-list jsoneditor-root' +
|
||||||
(data.selected ? ' jsoneditor-selected' : '')},
|
(eson._meta.selected ? ' jsoneditor-selected' : '')},
|
||||||
h(Node, {
|
h(Node, {
|
||||||
data,
|
eson,
|
||||||
events: state.events,
|
events: state.events,
|
||||||
options: props,
|
options: props,
|
||||||
path: [],
|
|
||||||
prop: null
|
prop: null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -313,7 +323,7 @@ export default class TreeMode extends Component {
|
||||||
*/
|
*/
|
||||||
getErrors () {
|
getErrors () {
|
||||||
if (this.state.compiledSchema) {
|
if (this.state.compiledSchema) {
|
||||||
const valid = this.state.compiledSchema(esonToJson(this.state.data))
|
const valid = this.state.compiledSchema(this.state.json)
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
return this.state.compiledSchema.errors.map(enrichSchemaError)
|
return this.state.compiledSchema.errors.map(enrichSchemaError)
|
||||||
}
|
}
|
||||||
|
@ -547,14 +557,14 @@ export default class TreeMode extends Component {
|
||||||
handleExpand = (path, expanded, recurse) => {
|
handleExpand = (path, expanded, recurse) => {
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
this.setState({
|
this.setState({
|
||||||
data: updateInEson(this.state.data, path, function (child) {
|
eson: updateIn(this.state.eson, path, function (child) {
|
||||||
return expand(child, (path) => true, expanded)
|
return expand(child, (path) => true, expanded)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.setState({
|
this.setState({
|
||||||
data: expand(this.state.data, path, expanded)
|
eson: expand(this.state.eson, path, expanded)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -885,10 +895,11 @@ export default class TreeMode extends Component {
|
||||||
set (json) {
|
set (json) {
|
||||||
// FIXME: when both json and expand are being changed via React, this.props must be updated before set(json) is called
|
// 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
|
// TODO: document option expand
|
||||||
const expand = this.props.expand || TreeMode.expandRoot
|
const expandCallback = this.props.expand || TreeMode.expandRoot
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
data: jsonToEson(json, expand, []),
|
json: json,
|
||||||
|
eson: expand(toEson2(json), expandCallback), // FIXME: expand eson
|
||||||
|
|
||||||
// TODO: do we want to keep history when .set(json) is called? (currently we remove history)
|
// TODO: do we want to keep history when .set(json) is called? (currently we remove history)
|
||||||
history: [],
|
history: [],
|
||||||
|
@ -901,7 +912,7 @@ export default class TreeMode extends Component {
|
||||||
* @returns {Object | Array | string | number | boolean | null} json
|
* @returns {Object | Array | string | number | boolean | null} json
|
||||||
*/
|
*/
|
||||||
get () {
|
get () {
|
||||||
return esonToJson(this.state.data)
|
return this.state.json
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
186
src/eson.js
186
src/eson.js
|
@ -5,7 +5,7 @@
|
||||||
* All functions are pure and don't mutate the ESON.
|
* All functions are pure and don't mutate the ESON.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { setIn, getIn, updateIn, deleteIn } from './utils/immutabilityHelpers'
|
import { setIn, getIn, updateIn, deleteIn, transform } from './utils/immutabilityHelpers'
|
||||||
import { isObject } from './utils/typeUtils'
|
import { isObject } from './utils/typeUtils'
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual'
|
||||||
import times from 'lodash/times'
|
import times from 'lodash/times'
|
||||||
|
@ -25,6 +25,51 @@ export const SELECTED_END = 2
|
||||||
export const SELECTED_BEFORE = 3
|
export const SELECTED_BEFORE = 3
|
||||||
export const SELECTED_AFTER = 4
|
export const SELECTED_AFTER = 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {JSONType} json
|
||||||
|
* @param {JSONPath} path
|
||||||
|
* @return {ESON}
|
||||||
|
*/
|
||||||
|
// TODO: rename to jsonToEson after refactoring of ESON is finished
|
||||||
|
export function toEson2 (json, path = []) {
|
||||||
|
const id = createId()
|
||||||
|
|
||||||
|
if (isObject(json)) {
|
||||||
|
let eson = {}
|
||||||
|
const keys = Object.keys(json)
|
||||||
|
keys.forEach((key) => eson[key] = toEson2(json[key], path.concat(key)))
|
||||||
|
eson._meta = { id, path, type: 'Object', keys }
|
||||||
|
return eson
|
||||||
|
}
|
||||||
|
else if (Array.isArray(json)) {
|
||||||
|
let eson = {}
|
||||||
|
json.forEach((value, index) => eson[index] = toEson2(value, path.concat(index)))
|
||||||
|
eson._meta = { id, path, type: 'Array', length: json.length }
|
||||||
|
return eson
|
||||||
|
}
|
||||||
|
else { // json is a number, string, boolean, or null
|
||||||
|
return {
|
||||||
|
_meta: { id, path, type: 'value', value: json }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over an eson array
|
||||||
|
* @param {ESONArray} esonArray
|
||||||
|
* @param {function (value, index, array)} callback
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
export function mapEsonArray (esonArray, callback) {
|
||||||
|
const length = esonArray._meta.length
|
||||||
|
let result = []
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result[i] = callback(esonArray[i], i, esonArray)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand function which will expand all nodes
|
* Expand function which will expand all nodes
|
||||||
* @param {Path} path
|
* @param {Path} path
|
||||||
|
@ -49,7 +94,7 @@ export function jsonToEson (json, expand = expandAll, path: JSONPath = [], type:
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
items: json.map((child, index) => {
|
items: json.map((child, index) => {
|
||||||
return {
|
return {
|
||||||
id: getId(), // TODO: use id based on index (only has to be unique within this array)
|
id: createId(), // TODO: use id based on index (only has to be unique within this array)
|
||||||
value: jsonToEson(child, expand, path.concat(index))
|
value: jsonToEson(child, expand, path.concat(index))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -61,7 +106,7 @@ export function jsonToEson (json, expand = expandAll, path: JSONPath = [], type:
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
props: Object.keys(json).map((name, index) => {
|
props: Object.keys(json).map((name, index) => {
|
||||||
return {
|
return {
|
||||||
id: getId(), // TODO: use id based on index (only has to be unique within this array)
|
id: createId(), // TODO: use id based on index (only has to be unique within this array)
|
||||||
name,
|
name,
|
||||||
value: jsonToEson(json[name], expand, path.concat(name))
|
value: jsonToEson(json[name], expand, path.concat(name))
|
||||||
}
|
}
|
||||||
|
@ -207,31 +252,25 @@ export function deleteInEson (eson: ESON, jsonPath: JSONPath) : JSONType {
|
||||||
/**
|
/**
|
||||||
* Expand or collapse one or multiple items or properties
|
* Expand or collapse one or multiple items or properties
|
||||||
* @param {ESON} eson
|
* @param {ESON} eson
|
||||||
* @param {function(path: Path) : boolean | Path} callback
|
* @param {function(Path) : boolean | Path} filterCallback
|
||||||
* When a path, the object/array at this path will be expanded/collapsed
|
* When a path, the object/array at this path will be expanded/collapsed
|
||||||
* When a function, all objects and arrays for which callback
|
* When a function, all objects and arrays for which callback
|
||||||
* returns true will be expanded/collapsed
|
* returns true will be expanded/collapsed
|
||||||
* @param {boolean} [expanded=true] New expanded state: true to expand, false to collapse
|
* @param {boolean} [expanded=true] New expanded state: true to expand, false to collapse
|
||||||
* @return {ESON}
|
* @return {ESON}
|
||||||
*/
|
*/
|
||||||
export function expand (eson: ESON, callback: Path | (Path) => boolean, expanded: boolean = true) {
|
export function expand (eson, filterCallback, expanded = true) {
|
||||||
// console.log('expand', callback, expand)
|
// console.log('expand', callback, expand)
|
||||||
|
|
||||||
if (typeof callback === 'function') {
|
if (typeof filterCallback === 'function') {
|
||||||
return transform(eson, function (value: ESON, path: Path, root: ESON) : ESON {
|
return transform(eson, function (value, path) {
|
||||||
if (value.type === 'Array' || value.type === 'Object') {
|
return (value && value._meta && (value._meta.type === 'Array' || value._meta.type === 'Object') && filterCallback(path))
|
||||||
if (callback(path)) {
|
? setIn(value, ['_meta', 'expanded'], expanded)
|
||||||
return setIn(value, ['expanded'], expanded)
|
: value
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (Array.isArray(callback)) {
|
else if (Array.isArray(filterCallback)) {
|
||||||
const esonPath: Path = toEsonPath(eson, callback)
|
return setIn(eson, filterCallback.concat(['_meta', 'expanded']), expanded)
|
||||||
|
|
||||||
return setIn(eson, esonPath.concat(['expanded']), expanded)
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error('Callback function or path expected')
|
throw new Error('Callback function or path expected')
|
||||||
|
@ -511,54 +550,54 @@ function findSharedPath (path1: JSONPath, path2: JSONPath): JSONPath {
|
||||||
|
|
||||||
return path1.slice(0, i)
|
return path1.slice(0, i)
|
||||||
}
|
}
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Recursively transform ESON: a recursive "map" function
|
// * Recursively transform ESON: a recursive "map" function
|
||||||
* @param {ESON} eson
|
// * @param {ESON} eson
|
||||||
* @param {function(value: ESON, path: Path, root: ESON)} callback
|
// * @param {function(value: ESON, path: Path, root: ESON)} callback
|
||||||
* @return {ESON} Returns the transformed eson object
|
// * @return {ESON} Returns the transformed eson object
|
||||||
*/
|
// */
|
||||||
export function transform (eson: ESON, callback: RecurseCallback) : ESON {
|
// export function transform (eson: ESON, callback: RecurseCallback) : ESON {
|
||||||
return recurseTransform (eson, [], eson, callback)
|
// return recurseTransform (eson, [], eson, callback)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Recursively transform ESON
|
// * Recursively transform ESON
|
||||||
* @param {ESON} value
|
// * @param {ESON} value
|
||||||
* @param {JSONPath} path
|
// * @param {JSONPath} path
|
||||||
* @param {ESON} root The root object, object at path=[]
|
// * @param {ESON} root The root object, object at path=[]
|
||||||
* @param {function(value: ESON, path: Path, root: ESON)} callback
|
// * @param {function(value: ESON, path: Path, root: ESON)} callback
|
||||||
* @return {ESON} Returns the transformed eson object
|
// * @return {ESON} Returns the transformed eson object
|
||||||
*/
|
// */
|
||||||
function recurseTransform (value: ESON, path: JSONPath, root: ESON, callback: RecurseCallback) : ESON {
|
// function recurseTransform (value: ESON, path: JSONPath, root: ESON, callback: RecurseCallback) : ESON {
|
||||||
let updatedValue: ESON = callback(value, path, root)
|
// let updatedValue: ESON = callback(value, path, root)
|
||||||
|
//
|
||||||
if (value.type === 'Array') {
|
// if (value.type === 'Array') {
|
||||||
let updatedItems = updatedValue.items
|
// let updatedItems = updatedValue.items
|
||||||
|
//
|
||||||
updatedValue.items.forEach((item, index) => {
|
// updatedValue.items.forEach((item, index) => {
|
||||||
const updatedItem = recurseTransform(item.value, path.concat(String(index)), root, callback)
|
// const updatedItem = recurseTransform(item.value, path.concat(String(index)), root, callback)
|
||||||
updatedItems = setIn(updatedItems, [index, 'value'], updatedItem)
|
// updatedItems = setIn(updatedItems, [index, 'value'], updatedItem)
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
updatedValue = setIn(updatedValue, ['items'], updatedItems)
|
// updatedValue = setIn(updatedValue, ['items'], updatedItems)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (value.type === 'Object') {
|
// if (value.type === 'Object') {
|
||||||
let updatedProps = updatedValue.props
|
// let updatedProps = updatedValue.props
|
||||||
|
//
|
||||||
updatedValue.props.forEach((prop, index) => {
|
// updatedValue.props.forEach((prop, index) => {
|
||||||
const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
|
// const updatedItem = recurseTransform(prop.value, path.concat(prop.name), root, callback)
|
||||||
updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
|
// updatedProps = setIn(updatedProps, [index, 'value'], updatedItem)
|
||||||
})
|
// })
|
||||||
|
//
|
||||||
updatedValue = setIn(updatedValue, ['props'], updatedProps)
|
// updatedValue = setIn(updatedValue, ['props'], updatedProps)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// (for type 'string' or 'value' there are no childs to traverse)
|
// // (for type 'string' or 'value' there are no childs to traverse)
|
||||||
|
//
|
||||||
return updatedValue
|
// return updatedValue
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively loop over a ESON object: a recursive "forEach" function.
|
* Recursively loop over a ESON object: a recursive "forEach" function.
|
||||||
|
@ -709,7 +748,7 @@ export function compileJSONPointer (path: Path) {
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move getId and createUniqueId to a separate file
|
// TODO: move createId to a separate file
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do a case insensitive search for a search text in a text
|
* Do a case insensitive search for a search text in a text
|
||||||
|
@ -725,19 +764,8 @@ export function containsCaseInsensitive (text: string, search: string): boolean
|
||||||
* Get a new "unique" id. Id's are created from an incremental counter.
|
* Get a new "unique" id. Id's are created from an incremental counter.
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
// TODO: use createUniqueId instead of getId()
|
export function createId () : number {
|
||||||
export function getId () : number {
|
|
||||||
_id++
|
_id++
|
||||||
return _id
|
return _id
|
||||||
}
|
}
|
||||||
let _id = 0
|
let _id = 0
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a unique id from an array with properties each having an id field.
|
|
||||||
* The
|
|
||||||
* @param {{id: string}} array
|
|
||||||
*/
|
|
||||||
// TODO: use createUniqueId instead of getId()
|
|
||||||
function createUniqueId (array) {
|
|
||||||
return Math.max(...array.map(item => item.id)) + 1
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
jsonToEson, esonToJson, toEsonPath,
|
jsonToEson, esonToJson, toEsonPath,
|
||||||
getInEson, setInEson, deleteInEson,
|
getInEson, setInEson, deleteInEson,
|
||||||
parseJSONPointer, compileJSONPointer,
|
parseJSONPointer, compileJSONPointer,
|
||||||
expandAll, pathExists, resolvePathIndex, findPropertyIndex, findNextProp, getId
|
expandAll, pathExists, resolvePathIndex, findPropertyIndex, findNextProp, createId
|
||||||
} from './eson'
|
} from './eson'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,7 +197,7 @@ export function remove (data: ESON, path: string) {
|
||||||
* @return {{data: ESON, revert: ESONPatch}}
|
* @return {{data: ESON, revert: ESONPatch}}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function add (data: ESON, path: string, value: ESON, options, id = getId()) {
|
export function add (data: ESON, path: string, value: ESON, options, id = createId()) {
|
||||||
const pathArray = parseJSONPointer(path)
|
const pathArray = parseJSONPointer(path)
|
||||||
const parentPath = pathArray.slice(0, pathArray.length - 1)
|
const parentPath = pathArray.slice(0, pathArray.length - 1)
|
||||||
const esonPath = toEsonPath(data, parentPath)
|
const esonPath = toEsonPath(data, parentPath)
|
||||||
|
|
18
src/types.js
18
src/types.js
|
@ -20,8 +20,12 @@
|
||||||
* ace: Object?
|
* ace: Object?
|
||||||
* }} Options
|
* }} Options
|
||||||
*
|
*
|
||||||
|
* @typedef {string[]} Path
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// FIXME: redefine all ESON related types
|
||||||
|
|
||||||
|
|
||||||
/**************************** GENERIC JSON TYPES ******************************/
|
/**************************** GENERIC JSON TYPES ******************************/
|
||||||
|
|
||||||
|
@ -48,24 +52,32 @@ export type ESONArrayItem = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ESONObject = {
|
export type ESONObject = {
|
||||||
|
_meta: {
|
||||||
type: 'Object',
|
type: 'Object',
|
||||||
|
path: JSONPath,
|
||||||
expanded?: boolean,
|
expanded?: boolean,
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
props: ESONObjectProperty[]
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ESONArray = {
|
export type ESONArray = {
|
||||||
|
_meta: {
|
||||||
type: 'Array',
|
type: 'Array',
|
||||||
|
path: JSONPath,
|
||||||
expanded?: boolean,
|
expanded?: boolean,
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
items: ESONArrayItem[]
|
length: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ESONValue = {
|
export type ESONValue = {
|
||||||
|
_meta: {
|
||||||
type: 'value' | 'string',
|
type: 'value' | 'string',
|
||||||
value?: any,
|
path: JSONPath,
|
||||||
|
value: null | boolean | string | number,
|
||||||
selected?: boolean,
|
selected?: boolean,
|
||||||
searchResult?: SearchResultStatus
|
searchResult?: SearchResultStatus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ESON = ESONObject | ESONArray | ESONValue
|
export type ESON = ESONObject | ESONArray | ESONValue
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import clone from 'lodash/clone'
|
import clone from 'lodash/clone'
|
||||||
import { isObjectOrArray } from './typeUtils'
|
import { isObjectOrArray, isObject } from './typeUtils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutability helpers
|
* Immutability helpers
|
||||||
|
@ -11,6 +11,7 @@ import { isObjectOrArray } from './typeUtils'
|
||||||
* https://www.npmjs.com/package/seamless-immutable
|
* https://www.npmjs.com/package/seamless-immutable
|
||||||
* https://www.npmjs.com/package/ih
|
* https://www.npmjs.com/package/ih
|
||||||
* https://www.npmjs.com/package/mutatis
|
* https://www.npmjs.com/package/mutatis
|
||||||
|
* https://github.com/mariocasciaro/object-path-immutable
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,6 +71,35 @@ export function setIn (object, path, value) {
|
||||||
return updatedObject
|
return updatedObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transform (object, callback, path = []) {
|
||||||
|
const updated = callback(object, path)
|
||||||
|
|
||||||
|
if (Array.isArray(updated)) {
|
||||||
|
let changed = false
|
||||||
|
let updatedItems = []
|
||||||
|
for (let i = 0; i < updated.length; i++) {
|
||||||
|
updatedItems[i] = transform(updated[i], callback, path.concat(i))
|
||||||
|
changed = changed || updatedItems[i] !== updated[i]
|
||||||
|
}
|
||||||
|
return changed ? updatedItems : updated
|
||||||
|
}
|
||||||
|
else if (isObject(updated)) {
|
||||||
|
let changed = false
|
||||||
|
let updatedProps = {}
|
||||||
|
for (let key in updated) {
|
||||||
|
if (updated.hasOwnProperty(key)) {
|
||||||
|
updatedProps[key] = transform(updated[key], callback, path.concat(key))
|
||||||
|
changed = changed || updatedProps[key] !== updated[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed ? updatedProps : updated
|
||||||
|
}
|
||||||
|
else { // updated is a value
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to replace a nested property in an object with a new value
|
* helper function to replace a nested property in an object with a new value
|
||||||
* without mutating the object itself.
|
* without mutating the object itself.
|
||||||
|
|
|
@ -10,7 +10,21 @@
|
||||||
export function isObject (value) {
|
export function isObject (value) {
|
||||||
return typeof value === 'object' &&
|
return typeof value === 'object' &&
|
||||||
value !== null &&
|
value !== null &&
|
||||||
!Array.isArray(value)
|
!Array.isArray(value) &&
|
||||||
|
(!value._meta || typeof value._meta.value === 'undefined')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether a value is not an object or array, but null, number, string, or
|
||||||
|
* boolean.
|
||||||
|
* @param {*} value
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export function isValue (value) {
|
||||||
|
return (value === null ||
|
||||||
|
typeof value === 'number' ||
|
||||||
|
typeof value === 'string' ||
|
||||||
|
typeof value === 'boolean')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,10 +4,12 @@ import { setIn, getIn } from '../src/utils/immutabilityHelpers'
|
||||||
import {
|
import {
|
||||||
jsonToEson, esonToJson, toEsonPath, toJsonPath, pathExists, transform, traverse,
|
jsonToEson, esonToJson, toEsonPath, toJsonPath, pathExists, transform, traverse,
|
||||||
parseJSONPointer, compileJSONPointer,
|
parseJSONPointer, compileJSONPointer,
|
||||||
|
toEson2,
|
||||||
expand, addErrors, search, applySearchResults, nextSearchResult, previousSearchResult,
|
expand, addErrors, search, applySearchResults, nextSearchResult, previousSearchResult,
|
||||||
applySelection, pathsFromSelection,
|
applySelection, pathsFromSelection,
|
||||||
SELECTED, SELECTED_END
|
SELECTED, SELECTED_END
|
||||||
} from '../src/eson'
|
} from '../src/eson'
|
||||||
|
import deepMap from "deep-map/lib/index"
|
||||||
|
|
||||||
const JSON1 = loadJSON('./resources/json1.json')
|
const JSON1 = loadJSON('./resources/json1.json')
|
||||||
const ESON1 = loadJSON('./resources/eson1.json')
|
const ESON1 = loadJSON('./resources/eson1.json')
|
||||||
|
@ -35,6 +37,25 @@ test('toJsonPath', t => {
|
||||||
t.deepEqual(toJsonPath(ESON1, esonPath), jsonPath)
|
t.deepEqual(toJsonPath(ESON1, esonPath), jsonPath)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('toEson2', t => {
|
||||||
|
t.deepEqual(replaceIds2(toEson2(1)), {_meta: {id: '[ID]', path: [], type: 'value', value: 1}})
|
||||||
|
t.deepEqual(replaceIds2(toEson2("foo")), {_meta: {id: '[ID]', path: [], type: 'value', value: "foo"}})
|
||||||
|
t.deepEqual(replaceIds2(toEson2(null)), {_meta: {id: '[ID]', path: [], type: 'value', value: null}})
|
||||||
|
t.deepEqual(replaceIds2(toEson2(false)), {_meta: {id: '[ID]', path: [], type: 'value', value: false}})
|
||||||
|
t.deepEqual(replaceIds2(toEson2({a:1, b: 2})), {
|
||||||
|
_meta: {id: '[ID]', path: [], type: 'Object', keys: ['a', 'b']},
|
||||||
|
a: {_meta: {id: '[ID]', path: ['a'], type: 'value', value: 1}},
|
||||||
|
b: {_meta: {id: '[ID]', path: ['b'], type: 'value', value: 2}}
|
||||||
|
})
|
||||||
|
|
||||||
|
printJSON(replaceIds2(toEson2([1,2])))
|
||||||
|
t.deepEqual(replaceIds2(toEson2([1,2])), {
|
||||||
|
_meta: {id: '[ID]', path: [], type: 'Array', length: 2},
|
||||||
|
0: {_meta: {id: '[ID]', path: [0], type: 'value', value: 1}},
|
||||||
|
1: {_meta: {id: '[ID]', path: [1], type: 'value', value: 2}}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('jsonToEson', t => {
|
test('jsonToEson', t => {
|
||||||
function expand (path) {
|
function expand (path) {
|
||||||
return true
|
return true
|
||||||
|
@ -396,6 +417,11 @@ function replaceIds (data, value = '[ID]') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function to replace all id properties with a constant value
|
||||||
|
function replaceIds2 (data, key = 'id', value = '[ID]') {
|
||||||
|
return deepMap(data, (v, k) => k === key ? value : v)
|
||||||
|
}
|
||||||
|
|
||||||
// helper function to print JSON in the console
|
// helper function to print JSON in the console
|
||||||
function printJSON (json, message = null) {
|
function printJSON (json, message = null) {
|
||||||
if (message) {
|
if (message) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import test from 'ava';
|
import test from 'ava';
|
||||||
import { getIn, setIn, updateIn, deleteIn, insertAt } from '../src/utils/immutabilityHelpers'
|
import { getIn, setIn, updateIn, deleteIn, insertAt, transform } from '../src/utils/immutabilityHelpers'
|
||||||
|
|
||||||
|
|
||||||
test('getIn', t => {
|
test('getIn', t => {
|
||||||
|
@ -276,3 +276,25 @@ test('insertAt', t => {
|
||||||
const updated = insertAt(obj, ['a', '2'], 8)
|
const updated = insertAt(obj, ['a', '2'], 8)
|
||||||
t.deepEqual(updated, {a: [1,2,8,3]})
|
t.deepEqual(updated, {a: [1,2,8,3]})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('transform (no change)', t => {
|
||||||
|
const obj = { a: [1,2,3]}
|
||||||
|
|
||||||
|
const updated = transform(obj, (value, path) => value)
|
||||||
|
t.deepEqual(updated, obj)
|
||||||
|
t.is(updated, obj)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('transform (change based on value)', t => {
|
||||||
|
const obj = { a: [1,2,3]}
|
||||||
|
|
||||||
|
const updated = transform(obj, (value, path) => value === 2 ? 20 : value)
|
||||||
|
t.deepEqual(updated, { a: [1,20,3]})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('transform (change based on path)', t => {
|
||||||
|
const obj = { a: [1,2,3]}
|
||||||
|
|
||||||
|
const updated = transform(obj, (value, path) => path.join('.') === 'a.1' ? 20 : value)
|
||||||
|
t.deepEqual(updated, { a: [1,20,3]})
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue