Add conditional rendering

This commit is contained in:
Misode
2020-05-26 17:25:42 +02:00
parent a6661a8563
commit 8371e43276
9 changed files with 64 additions and 25 deletions

View File

@@ -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({

View File

@@ -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()) {

View File

@@ -1,11 +1,14 @@
import { DataModel } from "./DataModel"
type PathElement = (string | number)
export class Path implements Iterable<PathElement> {
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<PathElement> {
}
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 {

View File

@@ -5,7 +5,8 @@ import { TreeView } from "../view/TreeView"
export interface INode<T> {
setParent: (parent: INode<any>) => void
default: IDefault<T>
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<T> = (value?: T) => T | undefined
export type ITransform<T> = (value: T) => any
export type IEnable = (path: Path) => boolean
export type IForce = () => boolean
export interface NodeMods<T> {
default?: IDefault<T>
transform?: ITransform<T>
enable?: IEnable
force?: IForce
}
export abstract class AbstractNode<T> implements INode<T> {
parent?: INode<any>
defaultMod: IDefault<T>
transformMod: ITransform<T>
enableMod: IEnable
forceMod: IForce
constructor(mods?: NodeMods<T>) {
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<any>) {
@@ -58,11 +67,24 @@ export abstract class AbstractNode<T> implements INode<T> {
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)
})

View File

@@ -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<boolean> {
force?: boolean
}
export class BooleanNode extends AbstractNode<boolean> {
force: boolean
constructor(mods?: BooleanNodeMods) {
constructor(mods?: NodeMods<boolean>) {
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 ? `` : `<label>${path.last()}</label>`}
<button${value === false ? ' style="font-weight: bold"' : ' '} data-id="${falseButton}">False</button>

View File

@@ -7,7 +7,10 @@ export class EnumNode extends AbstractNode<string> implements StateNode<string>
protected options: string[]
constructor(options: string[], mods?: NodeMods<string> | string) {
super(typeof mods === 'string' ? {default: () => mods} : mods)
super(typeof mods === 'string' ? {
default: () => mods,
force: () => true
} : mods)
this.options = options
}

View File

@@ -19,12 +19,12 @@ export class ObjectNode extends AbstractNode<IObject> {
})
}
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;
}

View File

@@ -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 `<div>

View File

@@ -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() {