diff --git a/src/app/app.ts b/src/app/app.ts index 6f0b10b9..cdfa39e1 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -20,13 +20,15 @@ const predicateTree = new RootNode('predicate', { transform: (s: string) => (s === 'foo') ? {test: 'baz'} : s }), number: new NumberNode({integer: false, min: 0}), - range: new RangeNode(), + range: new RangeNode({ + enable: (path) => path.push('condition').get() === 'foo' + }), predicate: new ObjectNode({ type: new EnumNode(EntityCollection), nbt: new ResourceNode({ default: (v) => "hahaha" }), - test: new BooleanNode(), + test: new BooleanNode({force: () => true}), recipes: new MapNode( new StringNode(), new RangeNode({ diff --git a/src/model/DataModel.ts b/src/model/DataModel.ts index 0e03a59f..f5e3a5c6 100644 --- a/src/model/DataModel.ts +++ b/src/model/DataModel.ts @@ -24,6 +24,15 @@ export class DataModel { this.listeners.forEach(listener => listener.invalidated(this)) } + get(path: Path) { + let node = this.data; + for (let index of path) { + if (node === undefined) return node + node = node[index] + } + return node + } + set(path: Path, value: any) { let node = this.data; for (let index of path.pop()) { diff --git a/src/model/Path.ts b/src/model/Path.ts index b55017a1..247e75f6 100644 --- a/src/model/Path.ts +++ b/src/model/Path.ts @@ -1,11 +1,14 @@ +import { DataModel } from "./DataModel" type PathElement = (string | number) export class Path implements Iterable { private arr: PathElement[] + model?: DataModel - constructor(arr?: PathElement[]) { + constructor(arr?: PathElement[], model?: DataModel) { this.arr = arr || [] + this.model = model } last(): PathElement { @@ -13,15 +16,23 @@ export class Path implements Iterable { } pop(): Path { - return new Path(this.arr.slice(0, -1)) + return new Path(this.arr.slice(0, -1), this.model) } push(element: PathElement): Path { - return new Path([...this.arr, element]) + return new Path([...this.arr, element], this.model) } copy(): Path { - return new Path([...this.arr]) + return new Path([...this.arr], this.model) + } + + withModel(model: DataModel): Path { + return new Path([...this.arr], model) + } + + get(): any { + return this.model?.get(this) } toString(): string { diff --git a/src/nodes/AbstractNode.ts b/src/nodes/AbstractNode.ts index 8623a11c..b83eb342 100644 --- a/src/nodes/AbstractNode.ts +++ b/src/nodes/AbstractNode.ts @@ -5,7 +5,8 @@ import { TreeView } from "../view/TreeView" export interface INode { setParent: (parent: INode) => void default: IDefault - transform: (value: T) => any + transform: (path: Path, value: T) => any + enabled: (path: Path, model: DataModel) => boolean render: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string renderRaw: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string } @@ -25,20 +26,28 @@ export type NodeChildren = { export type IDefault = (value?: T) => T | undefined export type ITransform = (value: T) => any +export type IEnable = (path: Path) => boolean +export type IForce = () => boolean export interface NodeMods { default?: IDefault transform?: ITransform + enable?: IEnable + force?: IForce } export abstract class AbstractNode implements INode { parent?: INode defaultMod: IDefault transformMod: ITransform + enableMod: IEnable + forceMod: IForce constructor(mods?: NodeMods) { this.defaultMod = mods?.default ? mods.default : (v) => v this.transformMod = mods?.transform ? mods.transform : (v) => v + this.enableMod = mods?.enable ? mods.enable : () => true + this.forceMod = mods?.force ? mods.force : () => false } setParent(parent: INode) { @@ -58,11 +67,24 @@ export abstract class AbstractNode implements INode { return this.defaultMod(value) } - transform(value: T) { + transform(path: Path, value: T) { + if (!this.enabled(path)) return undefined + if (this.force()) value = this.default(value)! return this.transformMod(value) } + enabled(path: Path, model?: DataModel) { + if (model) path = path.withModel(model) + return this.enableMod(path.pop()) + } + + force(): boolean { + return this.forceMod() + } + render(path: Path, value: T, view: TreeView, options?: RenderOptions): string { + if (!this.enabled(path, view.model)) return '' + const id = view.register(el => { this.mounted(el, path, view) }) diff --git a/src/nodes/BooleanNode.ts b/src/nodes/BooleanNode.ts index 3a770f35..81ccfc7e 100644 --- a/src/nodes/BooleanNode.ts +++ b/src/nodes/BooleanNode.ts @@ -2,26 +2,20 @@ import { AbstractNode, NodeMods, RenderOptions } from "./AbstractNode"; import { Path } from "../model/Path"; import { TreeView } from "../view/TreeView"; -export interface BooleanNodeMods extends NodeMods { - force?: boolean -} - export class BooleanNode extends AbstractNode { - force: boolean - constructor(mods?: BooleanNodeMods) { + constructor(mods?: NodeMods) { super({ default: () => false, ...mods}) - this.force = (mods?.force === true) } renderRaw(path: Path, value: boolean, view: TreeView, options?: RenderOptions) { const falseButton = view.registerClick(el => { - view.model.set(path, !this.force && value === false ? undefined : false) + view.model.set(path, !this.force() && value === false ? undefined : false) }) const trueButton = view.registerClick(el => { - view.model.set(path, !this.force && value === true ? undefined : true) + view.model.set(path, !this.force() && value === true ? undefined : true) }) return `${options?.hideLabel ? `` : ``} False diff --git a/src/nodes/EnumNode.ts b/src/nodes/EnumNode.ts index 310fb85a..4f9655f2 100644 --- a/src/nodes/EnumNode.ts +++ b/src/nodes/EnumNode.ts @@ -7,7 +7,10 @@ export class EnumNode extends AbstractNode implements StateNode protected options: string[] constructor(options: string[], mods?: NodeMods | string) { - super(typeof mods === 'string' ? {default: () => mods} : mods) + super(typeof mods === 'string' ? { + default: () => mods, + force: () => true + } : mods) this.options = options } diff --git a/src/nodes/ObjectNode.ts b/src/nodes/ObjectNode.ts index cc423d97..7ea20c62 100644 --- a/src/nodes/ObjectNode.ts +++ b/src/nodes/ObjectNode.ts @@ -19,12 +19,12 @@ export class ObjectNode extends AbstractNode { }) } - transform(value: IObject) { + transform(path: Path, value: IObject) { if (value === undefined) return undefined value = value || {} let res: any = {} Object.keys(this.fields).forEach(f => - res[f] = this.fields[f].transform(value[f]) + res[f] = this.fields[f].transform(path.push(f), value[f]) ) return res; } diff --git a/src/nodes/RootNode.ts b/src/nodes/RootNode.ts index 19099d38..d4f33158 100644 --- a/src/nodes/RootNode.ts +++ b/src/nodes/RootNode.ts @@ -11,10 +11,6 @@ export class RootNode extends ObjectNode { this.id = id } - transform(value: IObject) { - return JSON.stringify(super.transform(value)) - } - render(path: Path, value: IObject, view: TreeView) { value = value || {} return `
diff --git a/src/view/SourceView.ts b/src/view/SourceView.ts index 54b3bb92..19bfaa4d 100644 --- a/src/view/SourceView.ts +++ b/src/view/SourceView.ts @@ -1,4 +1,5 @@ import { DataModel, ModelListener } from "../model/DataModel" +import { Path } from "../model/Path" export class SourceView implements ModelListener { model: DataModel @@ -11,7 +12,8 @@ export class SourceView implements ModelListener { } render() { - this.target.textContent = this.model.schema.transform(this.model.data) + const transformed = this.model.schema.transform(new Path([], this.model), this.model.data) + this.target.textContent = JSON.stringify(transformed) } invalidated() {