Halfway implementing AppendContextMenu. Upgraded to preact v5.6.0

This commit is contained in:
jos 2016-08-05 14:17:24 +02:00
parent 7f6e7459df
commit 9d61ce001a
4 changed files with 142 additions and 13 deletions

View File

@ -42,8 +42,7 @@
"mithril": "0.2.5", "mithril": "0.2.5",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "2.4.5", "mocha": "2.4.5",
"preact": "4.8.0", "preact": "5.6.0",
"react-dev-server": "0.6.2",
"reify": "0.3.6", "reify": "0.3.6",
"rollup": "0.34.1", "rollup": "0.34.1",
"rollup-plugin-buble": "0.12.1", "rollup-plugin-buble": "0.12.1",

View File

@ -32,6 +32,7 @@ export default class JSONNode extends Component {
this.handleKeyDownValue = this.handleKeyDownValue.bind(this) this.handleKeyDownValue = this.handleKeyDownValue.bind(this)
this.handleExpand = this.handleExpand.bind(this) this.handleExpand = this.handleExpand.bind(this)
this.handleContextMenu = this.handleContextMenu.bind(this) this.handleContextMenu = this.handleContextMenu.bind(this)
this.handleAppendContextMenu = this.handleAppendContextMenu.bind(this)
} }
render (props) { render (props) {
@ -67,6 +68,10 @@ export default class JSONNode extends Component {
}) })
}) })
if (props.length === 0) {
props.push(this.renderAppend('(empty object)'))
}
contents.push(h('ul', {class: 'jsoneditor-list'}, props)) contents.push(h('ul', {class: 'jsoneditor-list'}, props))
} }
@ -94,6 +99,10 @@ export default class JSONNode extends Component {
}) })
}) })
if (items.length === 0) {
items.push(this.renderAppend('(empty array)'))
}
contents.push(h('ul', {class: 'jsoneditor-list'}, items)) contents.push(h('ul', {class: 'jsoneditor-list'}, items))
} }
@ -103,7 +112,7 @@ export default class JSONNode extends Component {
renderJSONValue ({path, data, options}) { renderJSONValue ({path, data, options}) {
return h('li', {}, [ return h('li', {}, [
h('div', {class: 'jsoneditor-node'}, [ h('div', {class: 'jsoneditor-node'}, [
h('div', {class: 'jsoneditor-button-placeholder', contentEditable: 'false'}), this.renderPlaceholder(),
this.renderContextMenuButton(), this.renderContextMenuButton(),
this.renderProperty(path, data, options), this.renderProperty(path, data, options),
this.renderSeparator(), this.renderSeparator(),
@ -112,8 +121,27 @@ export default class JSONNode extends Component {
]) ])
} }
/**
* Render contents for an empty object or array
* @param {string} text
* @return {*}
*/
renderAppend (text) {
return h('li', {}, [
h('div', {class: 'jsoneditor-node'}, [
this.renderPlaceholder(),
this.renderAppendContextMenuButton(),
this.renderReadonly(text)
])
])
}
renderPlaceholder () {
return h('div', {class: 'jsoneditor-button-placeholder'})
}
renderReadonly (text, title = null) { renderReadonly (text, title = null) {
return h('div', {class: 'jsoneditor-readonly', contentEditable: 'false', title}, text) return h('div', {class: 'jsoneditor-readonly', title}, text)
} }
renderProperty (path, data, options) { renderProperty (path, data, options) {
@ -124,7 +152,6 @@ export default class JSONNode extends Component {
if (isIndex) { // array item if (isIndex) { // array item
return h('div', { return h('div', {
class: 'jsoneditor-property jsoneditor-readonly', class: 'jsoneditor-property jsoneditor-readonly',
contentEditable: 'false',
spellCheck: 'false' spellCheck: 'false'
}, prop) }, prop)
} }
@ -143,7 +170,6 @@ export default class JSONNode extends Component {
return h('div', { return h('div', {
class: 'jsoneditor-property jsoneditor-readonly', class: 'jsoneditor-property jsoneditor-readonly',
contentEditable: 'false',
spellCheck: 'false', spellCheck: 'false',
onInput: this.handleChangeProperty onInput: this.handleChangeProperty
}, content) }, content)
@ -151,7 +177,7 @@ export default class JSONNode extends Component {
} }
renderSeparator() { renderSeparator() {
return h('div', {class: 'jsoneditor-separator', contentEditable: 'false'}, ':') return h('div', {class: 'jsoneditor-separator'}, ':')
} }
renderValue (value) { renderValue (value) {
@ -172,7 +198,7 @@ export default class JSONNode extends Component {
renderExpandButton () { renderExpandButton () {
const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}` const className = `jsoneditor-button jsoneditor-${this.props.data.expanded ? 'expanded' : 'collapsed'}`
return h('div', {class: 'jsoneditor-button-container', contentEditable: 'false'}, return h('div', {class: 'jsoneditor-button-container'},
h('button', {class: className, onClick: this.handleExpand}) h('button', {class: className, onClick: this.handleExpand})
) )
} }
@ -181,7 +207,7 @@ export default class JSONNode extends Component {
const className = 'jsoneditor-button jsoneditor-contextmenu' + const className = 'jsoneditor-button jsoneditor-contextmenu' +
(this.props.data.contextMenu ? ' jsoneditor-visible' : '') (this.props.data.contextMenu ? ' jsoneditor-visible' : '')
return h('div', {class: 'jsoneditor-button-container', contentEditable: 'false'}, return h('div', {class: 'jsoneditor-button-container'},
this.props.data.contextMenu this.props.data.contextMenu
? this.renderContextMenu(this.props.data.contextMenu) ? this.renderContextMenu(this.props.data.contextMenu)
: null, : null,
@ -319,6 +345,64 @@ export default class JSONNode extends Component {
return h(ContextMenu, {anchor, root, items}) return h(ContextMenu, {anchor, root, items})
} }
renderAppendContextMenuButton () {
const className = 'jsoneditor-button jsoneditor-contextmenu' +
(this.props.data.contextMenu ? ' jsoneditor-visible' : '')
// FIXME: show context menu, add right handlers
return h('div', {class: 'jsoneditor-button-container'},
this.props.data.contextMenu
? this.renderAppendContextMenu(this.props.data.contextMenu)
: null,
h('button', {class: className, onClick: this.handleAppendContextMenu})
)
}
renderAppendContextMenu ({anchor, root}) {
const path = this.props.path
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) { shouldComponentUpdate(nextProps, nextState) {
// WARNING: we suppose that JSONNode is stateless, we don't check changes in the state, only in props // WARNING: we suppose that JSONNode is stateless, we don't check changes in the state, only in props
return Object.keys(nextProps).some(prop => this.props[prop] !== nextProps[prop]) return Object.keys(nextProps).some(prop => this.props[prop] !== nextProps[prop])
@ -379,6 +463,12 @@ export default class JSONNode extends Component {
} }
} }
handleAppendContextMenu(event) {
event.stopPropagation() // stop propagation, because else Main.js will hide the context menu again
this.props.events.onAppend(this.props.path, 'value')
}
/** /**
* When this JSONNode holds an URL as value, open this URL in a new browser tab * When this JSONNode holds an URL as value, open this URL in a new browser tab
* @param event * @param event

View File

@ -29,6 +29,7 @@ export default class Main extends Component {
onChangeValue: this.handleChangeValue, onChangeValue: this.handleChangeValue,
onChangeType: this.handleChangeType, onChangeType: this.handleChangeType,
onInsert: this.handleInsert, onInsert: this.handleInsert,
onAppend: this.handleAppend,
onDuplicate: this.handleDuplicate, onDuplicate: this.handleDuplicate,
onRemove: this.handleRemove, onRemove: this.handleRemove,
onSort: this.handleSort, onSort: this.handleSort,
@ -132,6 +133,41 @@ export default class Main extends Component {
} }
} }
handleAppend (path, type) {
console.log('handleAppend', path, type)
this.handleHideContextMenu() // TODO: should be handled by the contextmenu itself
const dataPath = toDataPath(this.state.data, path)
const object = getIn(this.state.data, dataPath)
if (object.type === 'array') {
this.setState({
data: updateIn(this.state.data, dataPath.concat(['items']), (items) => {
const updatedItems = items.slice(0)
updatedItems.push(createDataEntry(type))
return updatedItems
})
})
}
else { // object.type === 'object'
this.setState({
data: updateIn(this.state.data, dataPath.concat(['props']), (props) => {
const updatedProps = props.slice(0)
updatedProps.push({
name: '',
value: createDataEntry(type)
})
return updatedProps
})
})
}
}
handleDuplicate (path) { handleDuplicate (path) {
console.log('handleDuplicate', path) console.log('handleDuplicate', path)

View File

@ -30,15 +30,19 @@
}; };
editor.set(json, { editor.set(json, {
name: 'myObject', name: 'myObject',
// expand: function (path) { expand: function (path) {
// return true return true
// } }
}); });
// 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, {
expand: function (path) {
return true
}
});
console.timeEnd('set') console.timeEnd('set')
}; };