Refactored menus
This commit is contained in:
parent
339b73fe51
commit
88aed193c6
207
src/JSONNode.js
207
src/JSONNode.js
|
@ -1,6 +1,7 @@
|
|||
import { h, Component } from 'preact'
|
||||
|
||||
import ContextMenu from './ContextMenu'
|
||||
import ActionMenu from './menu/ActionMenu'
|
||||
import AppendActionMenu from './menu/AppendActionMenu'
|
||||
import { escapeHTML, unescapeHTML } from './utils/stringUtils'
|
||||
import { getInnerText } from './utils/domUtils'
|
||||
import { stringConvert, valueType, isUrl } from './utils/typeUtils'
|
||||
|
@ -56,7 +57,7 @@ export default class JSONNode extends Component {
|
|||
const contents = [
|
||||
h('div', {class: 'jsoneditor-node jsoneditor-object'}, [
|
||||
this.renderExpandButton(),
|
||||
this.renderContextMenuButton(),
|
||||
this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, data, options),
|
||||
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`)
|
||||
])
|
||||
|
@ -92,7 +93,7 @@ export default class JSONNode extends Component {
|
|||
const contents = [
|
||||
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
|
||||
this.renderExpandButton(),
|
||||
this.renderContextMenuButton(),
|
||||
this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, data, options),
|
||||
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`)
|
||||
])
|
||||
|
@ -126,7 +127,7 @@ export default class JSONNode extends Component {
|
|||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node'}, [
|
||||
this.renderPlaceholder(),
|
||||
this.renderContextMenuButton(),
|
||||
this.renderActionMenuButton(),
|
||||
this.renderProperty(prop, data, options),
|
||||
this.renderSeparator(),
|
||||
this.renderValue(data.value)
|
||||
|
@ -279,12 +280,12 @@ export default class JSONNode extends Component {
|
|||
)
|
||||
}
|
||||
|
||||
renderContextMenuButton () {
|
||||
renderActionMenuButton () {
|
||||
const className = 'jsoneditor-button jsoneditor-contextmenu' +
|
||||
(this.state.menu ? ' jsoneditor-visible' : '')
|
||||
|
||||
return h('div', {class: 'jsoneditor-button-container'}, [
|
||||
this.renderContextMenu(this.state.menu),
|
||||
this.renderActionMenu(),
|
||||
h('button', {class: className, onClick: this.handleContextMenu})
|
||||
])
|
||||
}
|
||||
|
@ -299,187 +300,33 @@ export default class JSONNode extends Component {
|
|||
])
|
||||
}
|
||||
|
||||
renderContextMenu () {
|
||||
if (!this.state.menu) {
|
||||
renderActionMenu () {
|
||||
if (this.state.menu) {
|
||||
return h(ActionMenu, {
|
||||
anchor: this.state.menu.anchor,
|
||||
root: this.state.menu.root,
|
||||
path: this.getPath(),
|
||||
type: this.props.data.type,
|
||||
events: this.props.events
|
||||
})
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
|
||||
const {anchor, root} = this.state.menu
|
||||
const path = this.getPath()
|
||||
const hasParent = this.props.parent !== null
|
||||
const type = this.props.data.type
|
||||
const events = this.props.events
|
||||
const items = [] // array with menu items
|
||||
|
||||
items.push({
|
||||
text: 'Type',
|
||||
title: 'Change the type of this field',
|
||||
className: 'jsoneditor-type-' + type,
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value' + (type == 'value' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => events.onChangeType(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array' + (type == 'Array' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => events.onChangeType(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object' + (type == 'Object' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => events.onChangeType(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string' + (type == 'string' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => events.onChangeType(path, 'string')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (type === 'Array' || type === 'Object') {
|
||||
var direction = ((this.sortOrder == 'asc') ? 'desc': 'asc')
|
||||
items.push({
|
||||
text: 'Sort',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type,
|
||||
className: 'jsoneditor-sort-' + direction,
|
||||
click: () => events.onSort(path),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Ascending',
|
||||
className: 'jsoneditor-sort-asc',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type + ' in ascending order',
|
||||
click: () => events.onSort(path, 'asc')
|
||||
},
|
||||
{
|
||||
text: 'Descending',
|
||||
className: 'jsoneditor-sort-desc',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type +' in descending order',
|
||||
click: () => events.onSort(path, 'desc')
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
if (hasParent) {
|
||||
if (items.length) {
|
||||
// create a separator
|
||||
items.push({
|
||||
'type': 'separator'
|
||||
})
|
||||
}
|
||||
|
||||
// create insert button
|
||||
items.push({
|
||||
text: 'Insert',
|
||||
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
||||
submenuTitle: 'Select the type of the item to be inserted',
|
||||
className: 'jsoneditor-insert',
|
||||
click: () => events.onInsert(path, 'value'),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value',
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => events.onInsert(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array',
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => events.onInsert(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object',
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => events.onInsert(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string',
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => events.onInsert(path, 'string')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// create duplicate button
|
||||
items.push({
|
||||
text: 'Duplicate',
|
||||
title: 'Duplicate this item (Ctrl+D)',
|
||||
className: 'jsoneditor-duplicate',
|
||||
click: () => events.onDuplicate(path)
|
||||
})
|
||||
|
||||
// create remove button
|
||||
items.push({
|
||||
text: 'Remove',
|
||||
title: 'Remove this item (Ctrl+Del)',
|
||||
className: 'jsoneditor-remove',
|
||||
click: () => events.onRemove(path)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: implement a hook to adjust the context menu
|
||||
|
||||
return h(ContextMenu, {anchor, root, items})
|
||||
}
|
||||
|
||||
renderAppendContextMenu () {
|
||||
if (!this.state.appendMenu) {
|
||||
if (this.state.appendMenu) {
|
||||
return h(AppendActionMenu, {
|
||||
anchor: this.state.menu.anchor,
|
||||
root: this.state.menu.root,
|
||||
path: this.getPath(),
|
||||
events: this.props.events
|
||||
})
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
|
||||
const {anchor, root} = this.state.appendMenu
|
||||
const path = this.getPath()
|
||||
const events = this.props.events
|
||||
const items = [] // array with menu items
|
||||
|
||||
// create insert button
|
||||
items.push({
|
||||
text: 'Insert',
|
||||
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
||||
submenuTitle: 'Select the type of the item to be inserted',
|
||||
className: 'jsoneditor-insert',
|
||||
click: () => events.onAppend(path, 'value'),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value',
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => events.onAppend(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array',
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => events.onAppend(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object',
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => events.onAppend(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string',
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => events.onAppend(path, 'string')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// TODO: implement a hook to adjust the context menu
|
||||
|
||||
return h(ContextMenu, {anchor, root, items})
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import { h, Component } from 'preact'
|
||||
import Menu from './Menu'
|
||||
import {
|
||||
createChangeType, createSort,
|
||||
createSeparator,
|
||||
createInsert, createDuplicate, createRemove
|
||||
} from './entries'
|
||||
|
||||
export default class ActionMenu extends Component {
|
||||
/**
|
||||
* @param {{anchor, root, path, type, events}} props
|
||||
* @param state
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
render (props, state) {
|
||||
let items = [] // array with menu items
|
||||
|
||||
items.push(createChangeType(props.path, props.type, props.events.onChangeType))
|
||||
|
||||
if (props.type === 'Array' || props.type === 'Object') {
|
||||
// FIXME: get current sort order (to display correct icon)
|
||||
const order = 'asc'
|
||||
items.push(createSort(props.path, order, props.events.onSort))
|
||||
}
|
||||
|
||||
const hasParent = props.path.length > 0
|
||||
if (hasParent) {
|
||||
items.push(createSeparator())
|
||||
items.push(createInsert(props.path, props.events.onInsert))
|
||||
items.push(createDuplicate(props.path, props.events.onDuplicate))
|
||||
items.push(createRemove(props.path, props.events.onRemove))
|
||||
}
|
||||
|
||||
// TODO: implement a hook to adjust the action menu
|
||||
|
||||
return h(Menu, {
|
||||
anchor: props.anchor,
|
||||
root: props.root,
|
||||
items
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { h, Component } from 'preact'
|
||||
import Menu from './Menu'
|
||||
import { createAppend } from './entries'
|
||||
|
||||
export default class AppendActionMenu extends Component {
|
||||
/**
|
||||
* @param {{anchor, root, path, events}} props
|
||||
* @param state
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
render (props, state) {
|
||||
const items = [
|
||||
createAppend(props.path, props.events.onAppend)
|
||||
]
|
||||
|
||||
// TODO: implement a hook to adjust the action menu
|
||||
|
||||
return h(Menu, {
|
||||
anchor: props.anchor,
|
||||
root: props.root,
|
||||
items
|
||||
})
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import { h, Component } from 'preact'
|
|||
|
||||
export let CONTEXT_MENU_HEIGHT = 240
|
||||
|
||||
export default class ContextMenu extends Component {
|
||||
export default class Menu extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
|
@ -23,9 +23,14 @@ export default class ContextMenu extends Component {
|
|||
|
||||
this.renderMenuItem = this.renderMenuItem.bind(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.props.items) {
|
||||
|
||||
/**
|
||||
* @param {{items: Array}} props
|
||||
* @param state
|
||||
* @return {*}
|
||||
*/
|
||||
render (props, state) {
|
||||
if (!props.items) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -36,7 +41,7 @@ export default class ContextMenu extends Component {
|
|||
((this.state.orientation === 'top') ? 'jsoneditor-contextmenu-top' : 'jsoneditor-contextmenu-bottom')
|
||||
|
||||
return h('div', {class: className},
|
||||
this.props.items.map(this.renderMenuItem)
|
||||
props.items.map(this.renderMenuItem)
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
// This file contains functions to create menu entries
|
||||
|
||||
// TYPE_TITLES with explanation for the different types
|
||||
const TYPE_TITLES = {
|
||||
'value': 'Item type "value". ' +
|
||||
'The item type is automatically determined from the value ' +
|
||||
'and can be a string, number, boolean, or null.',
|
||||
'Object': 'Item type "object". ' +
|
||||
'An object contains an unordered set of key/value pairs.',
|
||||
'Array': 'Item type "array". ' +
|
||||
'An array contains an ordered collection of values.',
|
||||
'string': 'Item type "string". ' +
|
||||
'Item type is not determined from the value, ' +
|
||||
'but always returned as string.'
|
||||
}
|
||||
|
||||
export function createChangeType (path, type, onChangeType) {
|
||||
return {
|
||||
text: 'Type',
|
||||
title: 'Change the type of this field',
|
||||
className: 'jsoneditor-type-' + type,
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value' + (type == 'value' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => onChangeType(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array' + (type == 'Array' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => onChangeType(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object' + (type == 'Object' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => onChangeType(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string' + (type == 'string' ? ' jsoneditor-selected' : ''),
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => onChangeType(path, 'string')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function createSort (path, order, onSort) {
|
||||
var direction = ((order == 'asc') ? 'desc': 'asc')
|
||||
return {
|
||||
text: 'Sort',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type,
|
||||
className: 'jsoneditor-sort-' + direction,
|
||||
click: () => onSort(path),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Ascending',
|
||||
className: 'jsoneditor-sort-asc',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type + ' in ascending order',
|
||||
click: () => onSort(path, 'asc')
|
||||
},
|
||||
{
|
||||
text: 'Descending',
|
||||
className: 'jsoneditor-sort-desc',
|
||||
title: 'Sort the childs of this ' + TYPE_TITLES.type +' in descending order',
|
||||
click: () => onSort(path, 'desc')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function createInsert (path, onInsert) {
|
||||
return {
|
||||
text: 'Insert',
|
||||
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
||||
submenuTitle: 'Select the type of the item to be inserted',
|
||||
className: 'jsoneditor-insert',
|
||||
click: () => onInsert(path, 'value'),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value',
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => onInsert(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array',
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => onInsert(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object',
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => onInsert(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string',
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => onInsert(path, 'string')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function createAppend (path, onAppend) {
|
||||
return {
|
||||
text: 'Insert',
|
||||
title: 'Insert a new item with type \'value\' after this item (Ctrl+Ins)',
|
||||
submenuTitle: 'Select the type of the item to be inserted',
|
||||
className: 'jsoneditor-insert',
|
||||
click: () => onAppend(path, 'value'),
|
||||
submenu: [
|
||||
{
|
||||
text: 'Value',
|
||||
className: 'jsoneditor-type-value',
|
||||
title: TYPE_TITLES.value,
|
||||
click: () => onAppend(path, 'value')
|
||||
},
|
||||
{
|
||||
text: 'Array',
|
||||
className: 'jsoneditor-type-Array',
|
||||
title: TYPE_TITLES.array,
|
||||
click: () => onAppend(path, 'Array')
|
||||
},
|
||||
{
|
||||
text: 'Object',
|
||||
className: 'jsoneditor-type-Object',
|
||||
title: TYPE_TITLES.object,
|
||||
click: () => onAppend(path, 'Object')
|
||||
},
|
||||
{
|
||||
text: 'String',
|
||||
className: 'jsoneditor-type-string',
|
||||
title: TYPE_TITLES.string,
|
||||
click: () => onAppend(path, 'string')
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function createDuplicate (path, onDuplicate) {
|
||||
return {
|
||||
text: 'Duplicate',
|
||||
title: 'Duplicate this item (Ctrl+D)',
|
||||
className: 'jsoneditor-duplicate',
|
||||
click: () => onDuplicate(path)
|
||||
}
|
||||
}
|
||||
|
||||
export function createRemove (path, onRemove) {
|
||||
return {
|
||||
text: 'Remove',
|
||||
title: 'Remove this item (Ctrl+Del)',
|
||||
className: 'jsoneditor-remove',
|
||||
click: () => onRemove(path)
|
||||
}
|
||||
}
|
||||
|
||||
export function createSeparator () {
|
||||
return {
|
||||
'type': 'separator'
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue