Add list node and register click events

This commit is contained in:
Misode
2020-05-25 16:51:49 +02:00
parent 56f3766a13
commit 25bfaf666a
8 changed files with 109 additions and 20 deletions

View File

@@ -5,6 +5,7 @@ import { StringNode } from '../nodes/StringNode'
import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
import { SourceView } from '../view/SourceView'
import { ListNode } from '../nodes/ListNode'
const EntityCollection = ['sheep', 'pig']
@@ -15,9 +16,24 @@ const predicateTree = new RootNode('predicate', {
predicate: new ObjectNode({
type: new EnumNode(EntityCollection),
nbt: new StringNode()
})
}),
effects: new ListNode(
new ObjectNode({
type: new EnumNode(EntityCollection),
nbt: new StringNode()
}, {
default: () => ({
type: 'sheep'
})
})
)
}, {
default: () => ({ condition: 'foo', predicate: { nbt: 'hi' } })
default: () => ({
condition: 'foo',
predicate: {
nbt: 'hi'
}
})
});
const model = new DataModel(predicateTree)

View File

@@ -12,7 +12,7 @@ export class DataModel {
constructor(schema: RootNode) {
this.schema = schema
this.data = schema.default
this.data = schema.default()
this.listeners = []
}
@@ -32,7 +32,18 @@ export class DataModel {
}
node = node[index]
}
node[path.last()] = value
console.log('Set', path.toString(), JSON.stringify(value))
if (value === undefined) {
if (typeof path.last() === 'number') {
node.splice(path.last(), 1)
} else {
delete node[path.last()]
}
} else {
node[path.last()] = value
}
this.invalidate()
}

View File

@@ -4,8 +4,13 @@ import { TreeView } from "../view/TreeView"
export interface INode<T> {
setParent: (parent: INode<any>) => void
default: () => T | null
transform: (value: T) => any
render: (path: Path, value: T, view: TreeView) => string
render: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string
}
export type RenderOptions = {
hideLabel?: boolean
}
export type NodeChildren = {
@@ -19,11 +24,11 @@ export type NodeMods<T> = {
export abstract class AbstractNode<T> implements INode<T> {
parent?: INode<any>
default?: T
default: () => T | null = () => null
transformMod = (v: T) => v
constructor(mods?: NodeMods<T>) {
if (mods?.default) this.default = mods.default()
if (mods?.default) this.default = mods.default
if (mods?.transform) this.transformMod = mods.transform
}
@@ -39,7 +44,10 @@ export abstract class AbstractNode<T> implements INode<T> {
}
mounted(el: Element, path: Path, view: TreeView) {
el.addEventListener('change', evt => this.updateModel(el, path, view.model))
el.addEventListener('change', evt => {
this.updateModel(el, path, view.model)
evt.stopPropagation()
})
}
updateModel(el: Element, path: Path, model: DataModel) {}
@@ -48,5 +56,5 @@ export abstract class AbstractNode<T> implements INode<T> {
return this.transformMod(value)
}
abstract render(path: Path, value: T, view: TreeView): string
abstract render(path: Path, value: T, view: TreeView, options?: any): string
}

View File

@@ -1,4 +1,4 @@
import { AbstractNode, NodeMods } from './AbstractNode'
import { AbstractNode, NodeMods, RenderOptions } from './AbstractNode'
import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
import { Path } from '../model/Path'
@@ -15,9 +15,10 @@ export class EnumNode extends AbstractNode<string> {
model.set(path, el.querySelector('select')?.value)
}
render(path: Path, value: string, view: TreeView) {
render(path: Path, value: string, view: TreeView, options?: RenderOptions) {
const id = view.register(el => (el as HTMLInputElement).value = value)
return this.wrap(path, view, `<span>${path.last()}</span>
return this.wrap(path, view, `
${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
<select data-id=${id}>
${this.options.map(o => `<option value="${o}">${o}</option>`).join('')}
</select>`)

43
src/nodes/ListNode.ts Normal file
View File

@@ -0,0 +1,43 @@
import { AbstractNode, NodeMods, INode } from './AbstractNode'
import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
import { Path } from '../model/Path'
import { IObject } from './ObjectNode'
export class ListNode extends AbstractNode<IObject[]> {
protected children: INode<any>
constructor(values: INode<any>, mods?: NodeMods<IObject[]>) {
super(mods)
this.children = values
}
updateModel(el: Element, path: Path, model: DataModel) {
model.set(path, el.querySelector('select')?.value)
}
render(path: Path, value: IObject[], view: TreeView) {
value = value || []
const button = view.registerClick(el => {
view.model.set(path, [...value, this.children.default()])
})
return this.wrap(path, view, `<label>${path.last()}:</label>
<button data-id="${button}">Add</button>
<div style="padding-left:8px">
${value.map((obj, index) => {
return this.renderEntry(path.push(index), obj, view)
}).join('')}
</div>`)
}
private renderEntry(path: Path, value: IObject, view: TreeView) {
const button = view.registerClick(el => {
view.model.set(path, undefined)
})
return `<div><button data-id="${button}">Remove</button>
${this.children.render(path, value, view, {hideLabel: true})}
</div>`
}
}

View File

@@ -1,4 +1,4 @@
import { AbstractNode, NodeChildren, NodeMods } from './AbstractNode'
import { AbstractNode, NodeChildren, NodeMods, RenderOptions } from './AbstractNode'
import { TreeView } from '../view/TreeView'
import { Path } from '../model/Path'
@@ -27,12 +27,15 @@ export class ObjectNode extends AbstractNode<IObject> {
return res;
}
render(path: Path, value: IObject, view: TreeView) {
render(path: Path, value: IObject, view: TreeView, options?: RenderOptions) {
if (value === undefined) return ``
return this.wrap(path, view, `<span>${path.last()}:</span><div style="padding-left:8px">
return this.wrap(path, view, `
${options?.hideLabel ? `` : `<label>${path.last()}:</label>
<div style="padding-left:8px">
`}
${Object.keys(this.fields).map(f => {
return this.fields[f].render(path.push(f), value[f], view)
}).join('')}
</div>`)
${options?.hideLabel ? `` : `</div>`}`)
}
}

View File

@@ -1,4 +1,4 @@
import { AbstractNode, NodeMods } from './AbstractNode'
import { AbstractNode, NodeMods, RenderOptions } from './AbstractNode'
import { Path } from '../model/Path'
import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
@@ -12,8 +12,9 @@ export class StringNode extends AbstractNode<string> {
model.set(path, el.querySelector('input')?.value)
}
render(path: Path, value: string, view: TreeView) {
return this.wrap(path, view,
`<span>${path.last()}</span> <input value="${value || ''}"></input>`)
render(path: Path, value: string, view: TreeView, options?: RenderOptions) {
return this.wrap(path, view, `
${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
<input value="${value || ''}"></input>`)
}
}

View File

@@ -31,6 +31,12 @@ export class TreeView implements ModelListener {
return id
}
registerClick(callback: (el: Element) => void): string {
return this.register(el => {
el.addEventListener('click', _ => callback(el))
})
}
render() {
this.target.innerHTML = this.model.schema.render(new Path(), this.model.data, this)
for (const id in this.registry) {