diff --git a/src/app/app.ts b/src/app/app.ts index 9e37b8dc..0b10c355 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -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) diff --git a/src/model/DataModel.ts b/src/model/DataModel.ts index 0f9112f1..7373db96 100644 --- a/src/model/DataModel.ts +++ b/src/model/DataModel.ts @@ -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() } diff --git a/src/nodes/AbstractNode.ts b/src/nodes/AbstractNode.ts index 17d8fc38..1e4771e9 100644 --- a/src/nodes/AbstractNode.ts +++ b/src/nodes/AbstractNode.ts @@ -4,8 +4,13 @@ import { TreeView } from "../view/TreeView" export interface INode { setParent: (parent: INode) => 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 = { export abstract class AbstractNode implements INode { parent?: INode - default?: T + default: () => T | null = () => null transformMod = (v: T) => v constructor(mods?: NodeMods) { - 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 implements INode { } 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 implements INode { return this.transformMod(value) } - abstract render(path: Path, value: T, view: TreeView): string + abstract render(path: Path, value: T, view: TreeView, options?: any): string } diff --git a/src/nodes/EnumNode.ts b/src/nodes/EnumNode.ts index d9b021c3..23178232 100644 --- a/src/nodes/EnumNode.ts +++ b/src/nodes/EnumNode.ts @@ -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 { 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, `${path.last()} + return this.wrap(path, view, ` + ${options?.hideLabel ? `` : ``} `) diff --git a/src/nodes/ListNode.ts b/src/nodes/ListNode.ts new file mode 100644 index 00000000..0cacbf62 --- /dev/null +++ b/src/nodes/ListNode.ts @@ -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 { + protected children: INode + + constructor(values: INode, mods?: NodeMods) { + 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, ` + +
+ ${value.map((obj, index) => { + return this.renderEntry(path.push(index), obj, view) + }).join('')} +
`) + } + + private renderEntry(path: Path, value: IObject, view: TreeView) { + const button = view.registerClick(el => { + view.model.set(path, undefined) + }) + return `
+ ${this.children.render(path, value, view, {hideLabel: true})} +
` + } +} diff --git a/src/nodes/ObjectNode.ts b/src/nodes/ObjectNode.ts index 66d441e9..d6847a4f 100644 --- a/src/nodes/ObjectNode.ts +++ b/src/nodes/ObjectNode.ts @@ -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 { 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, `${path.last()}:
+ return this.wrap(path, view, ` + ${options?.hideLabel ? `` : ` +
+ `} ${Object.keys(this.fields).map(f => { return this.fields[f].render(path.push(f), value[f], view) }).join('')} -
`) + ${options?.hideLabel ? `` : `
`}`) } } diff --git a/src/nodes/StringNode.ts b/src/nodes/StringNode.ts index 0cd1aff8..6c26a904 100644 --- a/src/nodes/StringNode.ts +++ b/src/nodes/StringNode.ts @@ -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 { model.set(path, el.querySelector('input')?.value) } - render(path: Path, value: string, view: TreeView) { - return this.wrap(path, view, - `${path.last()} `) + render(path: Path, value: string, view: TreeView, options?: RenderOptions) { + return this.wrap(path, view, ` + ${options?.hideLabel ? `` : ``} + `) } } diff --git a/src/view/TreeView.ts b/src/view/TreeView.ts index f4434d5d..7d9b5829 100644 --- a/src/view/TreeView.ts +++ b/src/view/TreeView.ts @@ -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) {