Implemented actions for ContextMenu (not all fully working yet)
This commit is contained in:
parent
c3c836fa89
commit
667d3f32aa
|
@ -45,7 +45,7 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONObject ({data, index, options, events}) {
|
renderJSONObject ({data, path, index, 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'}, [
|
||||||
|
@ -60,6 +60,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,
|
||||||
data: child,
|
data: child,
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
|
@ -72,7 +73,7 @@ export default class JSONNode extends Component {
|
||||||
return h('li', {}, contents)
|
return h('li', {}, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONArray ({data, index, options, events}) {
|
renderJSONArray ({data, path, index, 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'}, [
|
||||||
|
@ -87,6 +88,7 @@ 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,
|
||||||
data: child,
|
data: child,
|
||||||
index,
|
index,
|
||||||
options,
|
options,
|
||||||
|
@ -172,8 +174,10 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContextMenu ({anchor, root}) {
|
renderContextMenu ({anchor, root}) {
|
||||||
const hasParent = this.props.data.path !== ''
|
const path = this.props.path
|
||||||
|
const hasParent = path !== ''
|
||||||
const type = this.props.data.type
|
const type = this.props.data.type
|
||||||
|
const events = this.props.events
|
||||||
const items = [] // array with menu items
|
const items = [] // array with menu items
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
|
@ -185,35 +189,25 @@ export default class JSONNode extends Component {
|
||||||
text: 'Value',
|
text: 'Value',
|
||||||
className: 'jsoneditor-type-value' + (type == 'value' ? ' jsoneditor-selected' : ''),
|
className: 'jsoneditor-type-value' + (type == 'value' ? ' jsoneditor-selected' : ''),
|
||||||
title: TYPE_TITLES.value,
|
title: TYPE_TITLES.value,
|
||||||
click: function () {
|
click: () => events.onChangeType(path, 'value')
|
||||||
alert('value') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Array',
|
text: 'Array',
|
||||||
className: 'jsoneditor-type-array' + (type == 'array' ? ' jsoneditor-selected' : ''),
|
className: 'jsoneditor-type-array' + (type == 'array' ? ' jsoneditor-selected' : ''),
|
||||||
title: TYPE_TITLES.array,
|
title: TYPE_TITLES.array,
|
||||||
click: function () {
|
click: () => events.onChangeType(path, 'array')
|
||||||
alert('array') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Object',
|
text: 'Object',
|
||||||
className: 'jsoneditor-type-object' + (type == 'object' ? ' jsoneditor-selected' : ''),
|
className: 'jsoneditor-type-object' + (type == 'object' ? ' jsoneditor-selected' : ''),
|
||||||
title: TYPE_TITLES.object,
|
title: TYPE_TITLES.object,
|
||||||
click: function () {
|
click: () => events.onChangeType(path, 'object')
|
||||||
//node._onChangeType('object');
|
|
||||||
alert('object') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'String',
|
text: 'String',
|
||||||
className: 'jsoneditor-type-string' + (type == 'string' ? ' jsoneditor-selected' : ''),
|
className: 'jsoneditor-type-string' + (type == 'string' ? ' jsoneditor-selected' : ''),
|
||||||
title: TYPE_TITLES.string,
|
title: TYPE_TITLES.string,
|
||||||
click: function () {
|
click: () => events.onChangeType(path, 'string')
|
||||||
// node._onChangeType('string');
|
|
||||||
alert('string') // TODO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -224,28 +218,19 @@ export default class JSONNode extends Component {
|
||||||
text: 'Sort',
|
text: 'Sort',
|
||||||
title: 'Sort the childs of this ' + TYPE_TITLES.type,
|
title: 'Sort the childs of this ' + TYPE_TITLES.type,
|
||||||
className: 'jsoneditor-sort-' + direction,
|
className: 'jsoneditor-sort-' + direction,
|
||||||
click: function () {
|
click: () => events.onSort(path),
|
||||||
// node.sort(direction);
|
|
||||||
alert('sort') // TODO
|
|
||||||
},
|
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Ascending',
|
text: 'Ascending',
|
||||||
className: 'jsoneditor-sort-asc',
|
className: 'jsoneditor-sort-asc',
|
||||||
title: 'Sort the childs of this ' + TYPE_TITLES.type + ' in ascending order',
|
title: 'Sort the childs of this ' + TYPE_TITLES.type + ' in ascending order',
|
||||||
click: function () {
|
click: () => events.onSort(path, 'asc')
|
||||||
// node.sort('asc');
|
|
||||||
alert('asc') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Descending',
|
text: 'Descending',
|
||||||
className: 'jsoneditor-sort-desc',
|
className: 'jsoneditor-sort-desc',
|
||||||
title: 'Sort the childs of this ' + TYPE_TITLES.type +' in descending order',
|
title: 'Sort the childs of this ' + TYPE_TITLES.type +' in descending order',
|
||||||
click: function () {
|
click: () => events.onSort(path, 'desc')
|
||||||
// node.sort('desc');
|
|
||||||
alert('desc') // TODO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -265,46 +250,31 @@ export default class JSONNode extends Component {
|
||||||
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
||||||
submenuTitle: 'Select the type of the item to be inserted',
|
submenuTitle: 'Select the type of the item to be inserted',
|
||||||
className: 'jsoneditor-insert',
|
className: 'jsoneditor-insert',
|
||||||
click: function () {
|
click: () => events.onInsert(path, '', ''),
|
||||||
// node._onInsertBefore('', '', 'value');
|
|
||||||
alert('insert') // TODO
|
|
||||||
},
|
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
text: 'Value',
|
text: 'Value',
|
||||||
className: 'jsoneditor-type-value',
|
className: 'jsoneditor-type-value',
|
||||||
title: TYPE_TITLES.value,
|
title: TYPE_TITLES.value,
|
||||||
click: function () {
|
click: () => events.onInsert(path, '', '')
|
||||||
// node._onInsertBefore('', '', 'value');
|
|
||||||
alert('insert value') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Array',
|
text: 'Array',
|
||||||
className: 'jsoneditor-type-array',
|
className: 'jsoneditor-type-array',
|
||||||
title: TYPE_TITLES.array,
|
title: TYPE_TITLES.array,
|
||||||
click: function () {
|
click: () => events.onInsert(path, '', [])
|
||||||
// node._onInsertBefore('', []);
|
|
||||||
alert('insert array') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Object',
|
text: 'Object',
|
||||||
className: 'jsoneditor-type-object',
|
className: 'jsoneditor-type-object',
|
||||||
title: TYPE_TITLES.object,
|
title: TYPE_TITLES.object,
|
||||||
click: function () {
|
click: () => events.onInsert(path, '', {})
|
||||||
// node._onInsertBefore('', {});
|
|
||||||
alert('insert object') // TODO
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'String',
|
text: 'String',
|
||||||
className: 'jsoneditor-type-string',
|
className: 'jsoneditor-type-string',
|
||||||
title: TYPE_TITLES.string,
|
title: TYPE_TITLES.string,
|
||||||
click: function () {
|
click: () => events.onInsert(path, '', '', 'string')
|
||||||
// node._onInsertBefore('', '', 'string');
|
|
||||||
alert('insert string') // TODO
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -315,10 +285,7 @@ export default class JSONNode extends Component {
|
||||||
text: 'Duplicate',
|
text: 'Duplicate',
|
||||||
title: 'Duplicate this item (Ctrl+D)',
|
title: 'Duplicate this item (Ctrl+D)',
|
||||||
className: 'jsoneditor-duplicate',
|
className: 'jsoneditor-duplicate',
|
||||||
click: function () {
|
click: () => events.onDuplicate(path)
|
||||||
// Node.onDuplicate(node);
|
|
||||||
alert('duplicate') // TODO
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// create remove button
|
// create remove button
|
||||||
|
@ -326,10 +293,7 @@ export default class JSONNode extends Component {
|
||||||
text: 'Remove',
|
text: 'Remove',
|
||||||
title: 'Remove this item (Ctrl+Del)',
|
title: 'Remove this item (Ctrl+Del)',
|
||||||
className: 'jsoneditor-remove',
|
className: 'jsoneditor-remove',
|
||||||
click: function () {
|
click: () => events.onRemove(path)
|
||||||
//Node.onRemove(node);
|
|
||||||
alert('remove') // TODO
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,8 +321,8 @@ 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.data.path.lastIndexOf('/')
|
const index = this.props.path.lastIndexOf('/')
|
||||||
const path = this.props.data.path.substr(0, index)
|
const path = this.props.path.substr(0, index)
|
||||||
|
|
||||||
this.props.events.onChangeProperty(path, oldProp, newProp)
|
this.props.events.onChangeProperty(path, oldProp, newProp)
|
||||||
}
|
}
|
||||||
|
@ -366,7 +330,7 @@ export default class JSONNode extends Component {
|
||||||
handleChangeValue (event) {
|
handleChangeValue (event) {
|
||||||
const value = JSONNode._getValueFromEvent(event)
|
const value = JSONNode._getValueFromEvent(event)
|
||||||
|
|
||||||
this.props.events.onChangeValue(this.props.data.path, value)
|
this.props.events.onChangeValue(this.props.path, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickValue (event) {
|
handleClickValue (event) {
|
||||||
|
@ -382,7 +346,7 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExpand (event) {
|
handleExpand (event) {
|
||||||
this.props.events.onExpand(this.props.data.path, !this.props.data.expanded)
|
this.props.events.onExpand(this.props.path, !this.props.data.expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (event) {
|
handleContextMenu (event) {
|
||||||
|
@ -393,7 +357,7 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.props.events.showContextMenu({
|
this.props.events.showContextMenu({
|
||||||
path: this.props.data.path,
|
path: this.props.path,
|
||||||
anchor: event.target,
|
anchor: event.target,
|
||||||
root: JSONNode._findRootElement(event)
|
root: JSONNode._findRootElement(event)
|
||||||
})
|
})
|
||||||
|
|
211
src/Main.js
211
src/Main.js
|
@ -1,8 +1,10 @@
|
||||||
import { h, Component } from 'preact'
|
import { h, Component } from 'preact'
|
||||||
import * as pointer from 'json-pointer'
|
import * as pointer from 'json-pointer'
|
||||||
|
|
||||||
import { setIn, getIn } from './utils/objectUtils'
|
import { setIn, updateIn, getIn, deleteIn, cloneDeep } from './utils/objectUtils'
|
||||||
|
import { compareAsc, compareDesc } from './utils/arrayUtils'
|
||||||
import { isObject } from './utils/typeUtils'
|
import { isObject } from './utils/typeUtils'
|
||||||
|
import bindMethods from './utils/bindMethods'
|
||||||
import JSONNode from './JSONNode'
|
import JSONNode from './JSONNode'
|
||||||
|
|
||||||
export default class Main extends Component {
|
export default class Main extends Component {
|
||||||
|
@ -10,11 +12,7 @@ export default class Main extends Component {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
// TODO: create a function bindMethods(this)
|
// TODO: create a function bindMethods(this)
|
||||||
this.handleChangeProperty = this.handleChangeProperty.bind(this)
|
bindMethods(this)
|
||||||
this.handleChangeValue = this.handleChangeValue.bind(this)
|
|
||||||
this.handleExpand = this.handleExpand.bind(this)
|
|
||||||
this.handleShowContextMenu = this.handleShowContextMenu.bind(this)
|
|
||||||
this.handleHideContextMenu = this.handleHideContextMenu.bind(this)
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
options: Object.assign({
|
options: Object.assign({
|
||||||
|
@ -32,7 +30,14 @@ export default class Main extends Component {
|
||||||
events: {
|
events: {
|
||||||
onChangeProperty: this.handleChangeProperty,
|
onChangeProperty: this.handleChangeProperty,
|
||||||
onChangeValue: this.handleChangeValue,
|
onChangeValue: this.handleChangeValue,
|
||||||
|
onChangeType: this.handleChangeType,
|
||||||
|
onInsert: this.handleInsert,
|
||||||
|
onDuplicate: this.handleDuplicate,
|
||||||
|
onRemove: this.handleRemove,
|
||||||
|
onSort: this.handleSort,
|
||||||
|
|
||||||
onExpand: this.handleExpand,
|
onExpand: this.handleExpand,
|
||||||
|
|
||||||
showContextMenu: this.handleShowContextMenu,
|
showContextMenu: this.handleShowContextMenu,
|
||||||
hideContextMenu: this.handleHideContextMenu
|
hideContextMenu: this.handleHideContextMenu
|
||||||
},
|
},
|
||||||
|
@ -47,7 +52,12 @@ export default class Main extends Component {
|
||||||
render() {
|
render() {
|
||||||
return h('div', {class: 'jsoneditor', onClick: this.handleHideContextMenu}, [
|
return h('div', {class: 'jsoneditor', onClick: this.handleHideContextMenu}, [
|
||||||
h('ul', {class: 'jsoneditor-list'}, [
|
h('ul', {class: 'jsoneditor-list'}, [
|
||||||
h(JSONNode, this.state)
|
h(JSONNode, {
|
||||||
|
data: this.state.data,
|
||||||
|
events: this.state.events,
|
||||||
|
options: this.state.options,
|
||||||
|
path: ''
|
||||||
|
})
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -55,36 +65,137 @@ export default class Main extends Component {
|
||||||
handleChangeValue (path, value) {
|
handleChangeValue (path, value) {
|
||||||
console.log('handleChangeValue', path, value)
|
console.log('handleChangeValue', path, value)
|
||||||
|
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path)).concat('value')
|
this._setIn(path, ['value'], value)
|
||||||
|
|
||||||
this.setState({
|
|
||||||
data: setIn(this.state.data, modelPath, value)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeProperty (path, oldProp, newProp) {
|
handleChangeProperty (path, oldProp, newProp) {
|
||||||
console.log('handleChangeProperty', path, oldProp, newProp)
|
console.log('handleChangeProperty', path, oldProp, newProp)
|
||||||
|
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, pointer.parse(path))
|
const index = this._findIndex(path, oldProp)
|
||||||
const parent = getIn(this.state.data, modelPath)
|
|
||||||
const index = parent.childs.findIndex(child => child.prop === oldProp)
|
|
||||||
const newPath = path + '/' + pointer.escape(newProp)
|
const newPath = path + '/' + pointer.escape(newProp)
|
||||||
|
|
||||||
let data = this.state.data
|
this._setIn(path, ['childs', index, 'path'], newPath)
|
||||||
data = setIn(data, modelPath.concat(['childs', index, 'path']), newPath)
|
this._setIn(path, ['childs', index, 'prop'], newProp)
|
||||||
data = setIn(data, modelPath.concat(['childs', index, 'prop']), newProp)
|
}
|
||||||
|
|
||||||
this.setState({ data })
|
handleChangeType (path, type) {
|
||||||
|
console.log('handleChangeType', path, type)
|
||||||
|
|
||||||
|
this._setIn(path, ['type'], type)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInsert (path, prop, value, type) {
|
||||||
|
console.log('handleInsert', path, prop, value, type)
|
||||||
|
|
||||||
|
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
||||||
|
|
||||||
|
// TODO: this method is quite complicated. Can we simplify it?
|
||||||
|
|
||||||
|
const parsedPath = pointer.parse(path)
|
||||||
|
const afterProp = parsedPath[parsedPath.length - 1]
|
||||||
|
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
|
||||||
|
.map(entry => '/' + pointer.escape(entry)).join('')
|
||||||
|
|
||||||
|
const parent = this._getIn(parentPath)
|
||||||
|
|
||||||
|
const index = parent.type === 'array'
|
||||||
|
? parseInt(afterProp)
|
||||||
|
: this._findIndex(parentPath, afterProp)
|
||||||
|
|
||||||
|
this._updateIn(parentPath, ['childs'], function (childs) {
|
||||||
|
const updated = childs.slice(0)
|
||||||
|
const type = isObject(value) ? 'object' : Array.isArray(value) ? 'array' : (type || 'value')
|
||||||
|
const newEntry = {
|
||||||
|
expanded: true,
|
||||||
|
type,
|
||||||
|
prop,
|
||||||
|
value,
|
||||||
|
childs: []
|
||||||
|
}
|
||||||
|
|
||||||
|
updated.splice(index + 1, 0, newEntry)
|
||||||
|
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDuplicate (path) {
|
||||||
|
console.log('handleDuplicate', path)
|
||||||
|
|
||||||
|
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
||||||
|
|
||||||
|
// TODO: this method is quite complicated. Can we simplify it?
|
||||||
|
|
||||||
|
const parsedPath = pointer.parse(path)
|
||||||
|
const prop = parsedPath[parsedPath.length - 1]
|
||||||
|
const parentPath = parsedPath.slice(0, parsedPath.length - 1)
|
||||||
|
.map(entry => '/' + pointer.escape(entry)).join('')
|
||||||
|
|
||||||
|
const parent = this._getIn(parentPath)
|
||||||
|
|
||||||
|
const index = parent.type === 'array'
|
||||||
|
? parseInt(prop)
|
||||||
|
: this._findIndex(parentPath, prop)
|
||||||
|
|
||||||
|
this._updateIn(parentPath, ['childs'], function (childs) {
|
||||||
|
const updated = childs.slice(0)
|
||||||
|
const original = childs[index]
|
||||||
|
const duplicate = cloneDeep(original)
|
||||||
|
|
||||||
|
updated.splice(index + 1, 0, duplicate)
|
||||||
|
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRemove (path) {
|
||||||
|
console.log('handleRemove', path)
|
||||||
|
|
||||||
|
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
||||||
|
|
||||||
|
this._deleteIn(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param {'asc' | 'desc' | null} [order=null] If not provided, will toggle current ordering
|
||||||
|
*/
|
||||||
|
handleSort (path, order = null) {
|
||||||
|
console.log('handleSort', path, order)
|
||||||
|
|
||||||
|
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
|
||||||
|
|
||||||
|
const comparators = {
|
||||||
|
asc: compareAsc,
|
||||||
|
desc: compareDesc
|
||||||
|
}
|
||||||
|
|
||||||
|
let _order
|
||||||
|
if (order === 'asc' || order === 'desc') {
|
||||||
|
_order = order
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// toggle previous order
|
||||||
|
const current = this._getIn(path, ['order'])
|
||||||
|
_order = current !== 'asc' ? 'asc' : 'desc'
|
||||||
|
this._setIn(path, ['order'], _order)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateIn(path, ['childs'], function (childs) {
|
||||||
|
const ordered = childs.slice(0)
|
||||||
|
const compare = comparators[_order] || comparators['asc']
|
||||||
|
|
||||||
|
ordered.sort((a, b) => compare(a.value, b.value))
|
||||||
|
|
||||||
|
return ordered
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleExpand(path, expand) {
|
handleExpand(path, expand) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
console.log('handleExpand', path, expand)
|
||||||
|
|
||||||
console.log('handleExpand', path, modelPath)
|
this._setIn(path, ['expanded'], expand)
|
||||||
|
|
||||||
this.setState({
|
|
||||||
data: setIn(this.state.data, modelPath.concat('expanded'), expand)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,29 +208,62 @@ export default class Main extends Component {
|
||||||
handleShowContextMenu({path, anchor, root}) {
|
handleShowContextMenu({path, anchor, root}) {
|
||||||
let data = this.state.data
|
let data = this.state.data
|
||||||
|
|
||||||
|
// 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)
|
||||||
if (this.state.contextMenuPath !== null) {
|
if (this.state.contextMenuPath !== null) {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(this.state.contextMenuPath))
|
this._setIn(this.state.contextMenuPath, ['contextMenu'], null)
|
||||||
data = setIn(data, modelPath.concat('contextMenu'), false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// show new menu
|
// show new menu
|
||||||
if (typeof path === 'string') {
|
if (typeof path === 'string') {
|
||||||
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
this._setIn(path, ['contextMenu'], {anchor, root})
|
||||||
data = setIn(data, modelPath.concat('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: typeof path === 'string' ? path : null // store path of current menu, just to easily find it next time
|
||||||
data
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHideContextMenu (event) {
|
handleHideContextMenu () {
|
||||||
// FIXME: find a different way to show/hide the context menu. create a single instance in the Main, pass a reference to it into the JSON nodes?
|
// FIXME: find a different way to show/hide the context menu. create a single instance in the Main, pass a reference to it into the JSON nodes?
|
||||||
this.handleShowContextMenu({})
|
this.handleShowContextMenu({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getIn (path, modelProps = []) {
|
||||||
|
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
||||||
|
|
||||||
|
return getIn(this.state.data, modelPath.concat(modelProps))
|
||||||
|
}
|
||||||
|
|
||||||
|
_setIn (path, modelProps = [], value) {
|
||||||
|
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
data: setIn(this.state.data, modelPath.concat(modelProps), value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateIn (path, modelProps = [], callback) {
|
||||||
|
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
data: updateIn(this.state.data, modelPath.concat(modelProps), callback)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_deleteIn (path, modelProps = []) {
|
||||||
|
const modelPath = Main._pathToModelPath(this.state.data, Main._parsePath(path))
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
data: deleteIn(this.state.data, modelPath.concat(modelProps))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_findIndex(path, prop) {
|
||||||
|
const object = this._getIn(path)
|
||||||
|
return object.childs.findIndex(child => child.prop === prop)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: comment
|
// TODO: comment
|
||||||
get () {
|
get () {
|
||||||
return Main._modelToJson(this.state.data)
|
return Main._modelToJson(this.state.data)
|
||||||
|
@ -198,7 +342,6 @@ export default class Main extends Component {
|
||||||
return {
|
return {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
path,
|
|
||||||
prop,
|
prop,
|
||||||
childs: value.map((child, index) => Main._jsonToModel(path + '/' + index, null, child, expand))
|
childs: value.map((child, index) => Main._jsonToModel(path + '/' + index, null, child, expand))
|
||||||
}
|
}
|
||||||
|
@ -207,7 +350,6 @@ export default class Main extends Component {
|
||||||
return {
|
return {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
expanded: expand(path),
|
expanded: expand(path),
|
||||||
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 + '/' + pointer.escape(prop), prop, value[prop], expand)
|
||||||
|
@ -217,7 +359,6 @@ export default class Main extends Component {
|
||||||
else {
|
else {
|
||||||
return {
|
return {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
path,
|
|
||||||
prop,
|
prop,
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,9 +35,9 @@
|
||||||
|
|
||||||
// set json
|
// set json
|
||||||
document.getElementById('setJSON').onclick = function () {
|
document.getElementById('setJSON').onclick = function () {
|
||||||
// console.time('set')
|
console.time('set')
|
||||||
editor.set(largeJSON);
|
editor.set(largeJSON);
|
||||||
// console.timeEnd('set')
|
console.timeEnd('set')
|
||||||
};
|
};
|
||||||
|
|
||||||
// get json
|
// get json
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* type: string,
|
* type: string,
|
||||||
* expanded: boolean?,
|
* expanded: boolean?,
|
||||||
* menu: boolean?,
|
* menu: boolean?,
|
||||||
* path: string,
|
|
||||||
* prop: string?,
|
* prop: string?,
|
||||||
* value: *?,
|
* value: *?,
|
||||||
* childs: Model[]?
|
* childs: Model[]?
|
||||||
|
|
|
@ -6,3 +6,31 @@
|
||||||
export function last (array) {
|
export function last (array) {
|
||||||
return array[array.length - 1]
|
return array[array.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator to sort an array in ascending order
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* [4,2,5].sort(compareAsc) // [2,4,5]
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function compareAsc (a, b) {
|
||||||
|
return a > b ? 1 : a < b ? -1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator to sort an array in ascending order
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* [4,2,5].sort(compareDesc) // [5,4,2]
|
||||||
|
*
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export function compareDesc (a, b) {
|
||||||
|
return a > b ? -1 : a < b ? 1 : 0
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* Helper function to bind all methods of a class instance to the instance
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* import bindMethods from './bindMethods'
|
||||||
|
*
|
||||||
|
* class MyClass {
|
||||||
|
* constructor () {
|
||||||
|
* bindMethods(this)
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* myMethod () {
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {Object} instance Instance of an ES6 class or prototype
|
||||||
|
*/
|
||||||
|
export default function bindMethods (instance) {
|
||||||
|
const prototype = Object.getPrototypeOf(instance)
|
||||||
|
|
||||||
|
Object.getOwnPropertyNames(prototype).forEach(name => {
|
||||||
|
if (typeof instance[name] === 'function') {
|
||||||
|
instance[name] = instance[name].bind(instance);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,5 +1,13 @@
|
||||||
import { isObject } from './typeUtils'
|
import { isObject } from './typeUtils'
|
||||||
// TODO: unit test getIn
|
|
||||||
|
|
||||||
|
// inspiration:
|
||||||
|
//
|
||||||
|
// https://www.npmjs.com/package/seamless-immutable
|
||||||
|
// https://www.npmjs.com/package/ih
|
||||||
|
// https://www.npmjs.com/package/mutatis
|
||||||
|
|
||||||
|
// TODO: unit test clone
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flat clone the properties of an object or array
|
* Flat clone the properties of an object or array
|
||||||
|
@ -25,6 +33,34 @@ export function clone (value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: test cloneDeep
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep clone the properties of an object or array
|
||||||
|
* @param {Object | Array} value
|
||||||
|
* @return {Object | Array} Returns a deep clone of the object or Array
|
||||||
|
*/
|
||||||
|
export function cloneDeep (value) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(cloneDeep)
|
||||||
|
}
|
||||||
|
else if (isObject(value)) {
|
||||||
|
const cloned = {}
|
||||||
|
|
||||||
|
Object.keys(value).forEach(key => {
|
||||||
|
cloned[key] = cloneDeep(value[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
return cloned
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// a primitive value
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unit test getIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to get a nested property in an object or array
|
* helper function to get a nested property in an object or array
|
||||||
*
|
*
|
||||||
|
@ -67,15 +103,93 @@ export function setIn (object, path, value) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: change array into object and vice versa when key is a number/string
|
|
||||||
|
|
||||||
const key = path[0]
|
const key = path[0]
|
||||||
const child = (Array.isArray(object[key]) || isObject(object[key]))
|
let updated
|
||||||
? object[key]
|
if (typeof key === 'string' && !isObject(object)) {
|
||||||
: (typeof path[1] === 'number' ? [] : {})
|
updated = {} // change into an object
|
||||||
const updated = clone(object)
|
}
|
||||||
|
else if (typeof key === 'number' && !Array.isArray(object)) {
|
||||||
|
updated = [] // change into an array
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updated = clone(object)
|
||||||
|
}
|
||||||
|
|
||||||
updated[key] = setIn(child, path.slice(1), value)
|
updated[key] = setIn(updated[key], path.slice(1), value)
|
||||||
|
|
||||||
return updated
|
return updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: unit test updateIn
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper function to replace a nested property in an object with a new value
|
||||||
|
* without mutating the object itself.
|
||||||
|
*
|
||||||
|
* @param {Object | Array} object
|
||||||
|
* @param {Array.<string | number>} path
|
||||||
|
* @param {function} callback
|
||||||
|
* @return {Object | Array} Returns a new, updated object or array
|
||||||
|
*/
|
||||||
|
export function updateIn (object, path, callback) {
|
||||||
|
if (path.length === 0) {
|
||||||
|
return callback(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = path[0]
|
||||||
|
let updated
|
||||||
|
if (typeof key === 'string' && !isObject(object)) {
|
||||||
|
updated = {} // change into an object
|
||||||
|
}
|
||||||
|
else if (typeof key === 'number' && !Array.isArray(object)) {
|
||||||
|
updated = [] // change into an array
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
updated = clone(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated[key] = updateIn(updated[key], path.slice(1), callback)
|
||||||
|
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unit test deleteIn
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper function to delete a nested property in an object
|
||||||
|
* without mutating the object itself.
|
||||||
|
*
|
||||||
|
* @param {Object | Array} object
|
||||||
|
* @param {Array.<string | number>} path
|
||||||
|
* @return {Object | Array} Returns a new, updated object or array
|
||||||
|
*/
|
||||||
|
export function deleteIn (object, path) {
|
||||||
|
if (path.length === 0) {
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.length === 1) {
|
||||||
|
const key = path[0]
|
||||||
|
const updated = clone(object)
|
||||||
|
if (Array.isArray(updated)) {
|
||||||
|
updated.splice(key, 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
delete updated[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = path[0]
|
||||||
|
const child = object[key]
|
||||||
|
if (Array.isArray(child) || isObject(child)) {
|
||||||
|
const updated = clone(object)
|
||||||
|
updated[key] = deleteIn(child, path.slice(1))
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// child property doesn't exist. just do nothing
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue