Basic editor using preact
This commit is contained in:
parent
b4cf47b06f
commit
0d250cbdcd
15
package.json
15
package.json
|
@ -20,7 +20,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"watch": "gulp watch",
|
"watch": "gulp watch",
|
||||||
"test": "mocha test"
|
"test": "mocha test",
|
||||||
|
"build-next": "rollup -c"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "3.8.8",
|
"ajv": "3.8.8",
|
||||||
|
@ -28,14 +29,26 @@
|
||||||
"javascript-natural-sort": "0.7.1"
|
"javascript-natural-sort": "0.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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": "3.9.1",
|
||||||
"gulp-clean-css": "2.0.5",
|
"gulp-clean-css": "2.0.5",
|
||||||
"gulp-concat-css": "2.2.0",
|
"gulp-concat-css": "2.2.0",
|
||||||
"gulp-shell": "0.5.2",
|
"gulp-shell": "0.5.2",
|
||||||
"gulp-util": "3.0.7",
|
"gulp-util": "3.0.7",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
|
"mithril": "0.2.5",
|
||||||
"mkdirp": "0.5.1",
|
"mkdirp": "0.5.1",
|
||||||
"mocha": "2.4.5",
|
"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",
|
"uglify-js": "2.6.2",
|
||||||
"webpack": "1.12.14"
|
"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