Basic editor using preact
This commit is contained in:
parent
b4cf47b06f
commit
0d250cbdcd
15
package.json
15
package.json
|
@ -20,7 +20,8 @@
|
|||
"scripts": {
|
||||
"build": "gulp",
|
||||
"watch": "gulp watch",
|
||||
"test": "mocha test"
|
||||
"test": "mocha test",
|
||||
"build-next": "rollup -c"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "3.8.8",
|
||||
|
@ -28,14 +29,26 @@
|
|||
"javascript-natural-sort": "0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-transform-react-jsx": "6.8.0",
|
||||
"browserify": "13.0.1",
|
||||
"buble": "0.12.5",
|
||||
"debug": "2.2.0",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-clean-css": "2.0.5",
|
||||
"gulp-concat-css": "2.2.0",
|
||||
"gulp-shell": "0.5.2",
|
||||
"gulp-util": "3.0.7",
|
||||
"json-loader": "0.5.4",
|
||||
"mithril": "0.2.5",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "2.4.5",
|
||||
"preact": "4.8.0",
|
||||
"reify": "0.3.6",
|
||||
"rollup": "0.34.1",
|
||||
"rollup-plugin-buble": "0.12.1",
|
||||
"rollup-plugin-commonjs": "3.1.0",
|
||||
"rollup-plugin-node-resolve": "1.7.1",
|
||||
"rollup-plugin-npm": "2.0.0",
|
||||
"uglify-js": "2.6.2",
|
||||
"webpack": "1.12.14"
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
|||
.jsoneditor {
|
||||
border: 1px solid #3883fa;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.jsoneditor-node {
|
||||
/*border: 1px solid #555;*/
|
||||
font: 14px Arial;
|
||||
|
||||
/* flexbox setup */
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.jsoneditor-node > div {
|
||||
flex: 1 1 auto;
|
||||
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
ul.jsoneditor-list {
|
||||
list-style-type: none;
|
||||
padding-left: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.jsoneditor-key,
|
||||
.jsoneditor-value {
|
||||
min-width: 32px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.jsoneditor-key:focus,
|
||||
.jsoneditor-value:focus,
|
||||
.jsoneditor-key:hover,
|
||||
.jsoneditor-value:hover {
|
||||
background-color: #FFFFAB;
|
||||
border-color: #ff0;
|
||||
}
|
||||
|
||||
.jsoneditor-separator {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.jsoneditor-info {
|
||||
color: gray;
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
import { h, Component } from 'preact'
|
||||
import isObject from './utils/isObject'
|
||||
|
||||
export default class JSONNode extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.onValueInput = this.onValueInput.bind(this)
|
||||
}
|
||||
|
||||
render (props) {
|
||||
if (Array.isArray(props.value)) {
|
||||
return this.renderArray(props)
|
||||
}
|
||||
else if (isObject(props.value)) {
|
||||
return this.renderObject(props)
|
||||
}
|
||||
else {
|
||||
return this.renderValue(props)
|
||||
}
|
||||
}
|
||||
|
||||
renderObject ({field, value, onChangeValue}) {
|
||||
//console.log('JSONObject', field,value)
|
||||
|
||||
return h('li', {class: 'jsoneditor-object'}, [
|
||||
h('div', {class: 'jsoneditor-node'}, [
|
||||
h('div', {class: 'jsoneditor-field', contentEditable: true}, field),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {class: 'jsoneditor-info'}, '{' + Object.keys(value).length + '}')
|
||||
]),
|
||||
h('ul',
|
||||
{class: 'jsoneditor-list'},
|
||||
Object.keys(value).map(f => h(JSONNode, {parent: this, field: f, value: value[f], onChangeValue})))
|
||||
])
|
||||
}
|
||||
|
||||
renderArray ({field, value, onChangeValue}) {
|
||||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
|
||||
h('div', {class: 'jsoneditor-field', contentEditable: true}, field),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {class: 'jsoneditor-info'}, '{' + value.length + '}')
|
||||
]),
|
||||
h('ul',
|
||||
{class: 'jsoneditor-list'},
|
||||
value.map((v, f) => h(JSONNode, {parent: this, field: f, value: v, onChangeValue})))
|
||||
])
|
||||
}
|
||||
|
||||
renderValue ({field, value}) {
|
||||
//console.log('JSONValue', field, value)
|
||||
|
||||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node'}, [
|
||||
h('div', {class: 'jsoneditor-field', contentEditable: true}, field),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {
|
||||
class: 'jsoneditor-value',
|
||||
contentEditable: true,
|
||||
// 'data-path': JSON.stringify(this.getPath())
|
||||
onInput: this.onValueInput
|
||||
}, value)
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextProps.field !== this.props.field || nextProps.value !== this.props.value
|
||||
}
|
||||
|
||||
onValueInput (event) {
|
||||
const path = this.getPath()
|
||||
const value = event.target.innerHTML
|
||||
this.props.onChangeValue(path, value)
|
||||
}
|
||||
|
||||
getPath () {
|
||||
const path = []
|
||||
|
||||
let node = this
|
||||
while (node) {
|
||||
path.unshift(node.props.field)
|
||||
|
||||
node = node.props.parent
|
||||
}
|
||||
|
||||
path.shift() // remove the root node again (null)
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { h, Component } from 'preact'
|
||||
import setIn from './utils/setIn'
|
||||
import JSONNode from './JSONNode'
|
||||
|
||||
export default class Main extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
json: props.json || {}
|
||||
}
|
||||
|
||||
this.onChangeValue = this.onChangeValue.bind(this)
|
||||
}
|
||||
|
||||
render(props, state) {
|
||||
return h('div', {class: 'jsoneditor', onInput: this.onInput}, [
|
||||
h('ul', {class: 'jsoneditor-list'}, [
|
||||
h(JSONNode, {parent: null, field: null, value: state.json, onChangeValue: this.onChangeValue})
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
onChangeValue (path, value) {
|
||||
console.log('onChangeValue', path, value)
|
||||
this.setState({
|
||||
json: setIn(this.state.json, path, value)
|
||||
})
|
||||
}
|
||||
|
||||
get () {
|
||||
return this.state.json
|
||||
}
|
||||
|
||||
set (json) {
|
||||
this.setState({json})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { h, render } from 'preact'
|
||||
import Main from './Main'
|
||||
|
||||
/**
|
||||
* Factory function to create a new JSONEditor
|
||||
* @param container
|
||||
* @return {*}
|
||||
* @constructor
|
||||
*/
|
||||
export default function jsoneditor (container) {
|
||||
const elem = render(h(Main), container)
|
||||
return elem._component
|
||||
}
|
||||
|
||||
// TODO: UMD export
|
||||
|
||||
window.jsoneditor = jsoneditor
|
||||
|
||||
|
||||
// export JSONEditor
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import isObject from './isObject'
|
||||
|
||||
// TODO: unit test clone
|
||||
|
||||
/**
|
||||
* Flat clone the properties of an object or array
|
||||
* @param {Object | Array} value
|
||||
* @return {Object | Array} Returns a flat clone of the object or Array
|
||||
*/
|
||||
export default function clone (value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.slice(0)
|
||||
}
|
||||
else if (isObject(value)) {
|
||||
const cloned = {}
|
||||
|
||||
Object.keys(value).forEach(key => {
|
||||
cloned[key] = value[key]
|
||||
})
|
||||
|
||||
return cloned
|
||||
}
|
||||
else {
|
||||
// a primitive value
|
||||
return value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Test whether a value is an object (and not an Array or Date or primitive value)
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {boolean}
|
||||
*/
|
||||
export default function isObject (value) {
|
||||
return typeof value === 'object' &&
|
||||
value && // not null
|
||||
!Array.isArray(value) &&
|
||||
value.toString() === '[object Object]'
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import isObject from './isObject'
|
||||
import clone from './clone'
|
||||
|
||||
// TODO: unit test setIn
|
||||
|
||||
/**
|
||||
* 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 {*} value
|
||||
* @return {Object | Array} Returns a new, updated object or array
|
||||
*/
|
||||
export default function setIn (object, path, value) {
|
||||
if (path.length === 0) {
|
||||
return value
|
||||
}
|
||||
|
||||
// TODO: change array into object and vice versa when key is a number/string
|
||||
|
||||
const key = path[0]
|
||||
const child = (Array.isArray(object[key]) || isObject(object[key]))
|
||||
? object[key]
|
||||
: (typeof path[1] === 'number' ? [] : {})
|
||||
const updated = clone(object)
|
||||
|
||||
updated[key] = setIn(child, path.slice(1), value)
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
|
||||
window.setIn = setIn // TODO: cleanup
|
Loading…
Reference in New Issue