Add range node + default mods

This commit is contained in:
Misode
2020-05-26 12:33:56 +02:00
parent abbbc0bbfa
commit f77997f633
12 changed files with 146 additions and 25 deletions

View File

@@ -9,6 +9,7 @@ import { ListNode } from '../nodes/ListNode'
import { BooleanNode } from '../nodes/BooleanNode'
import { MapNode } from '../nodes/MapNode'
import { NumberNode } from '../nodes/NumberNode'
import { RangeNode } from '../nodes/custom/RangeNode'
const EntityCollection = ['sheep', 'pig']
@@ -17,15 +18,15 @@ const predicateTree = new RootNode('predicate', {
transform: (s: string) => (s === 'foo') ? {test: 'baz'} : s
}),
number: new NumberNode({integer: false, min: 0}),
range: new RangeNode(),
predicate: new ObjectNode({
type: new EnumNode(EntityCollection),
nbt: new StringNode(),
test: new BooleanNode(),
recipes: new MapNode(
new StringNode(),
new ObjectNode({
duration: new StringNode(),
flag: new BooleanNode()
new RangeNode({
default: (v) => RangeNode.isExact(v) ? 2 : v
})
)
}),

View File

@@ -21,7 +21,6 @@ export class DataModel {
}
invalidate() {
console.log(this.data)
this.listeners.forEach(listener => listener.invalidated(this))
}

View File

@@ -2,9 +2,12 @@ import { DataModel } from "../model/DataModel"
import { Path } from "../model/Path"
import { TreeView } from "../view/TreeView"
export type IDefault<T> = (value?: T) => T | undefined
export type ITransform<T> = (value: T) => any
export interface INode<T> {
setParent: (parent: INode<any>) => void
default: () => T
default: IDefault<T>
transform: (value: T) => any
render: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string
renderRaw: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string
@@ -24,18 +27,20 @@ export type NodeChildren = {
}
export interface NodeMods<T> {
default?: () => T
default?: IDefault<T>
transform?: (value: T) => any
}
export abstract class AbstractNode<T> implements INode<T> {
parent?: INode<any>
defaultMod: () => T
transformMod: (v: T) => T
defaultMod: IDefault<T>
transformMod: ITransform<T>
constructor(def: () => T, mods?: NodeMods<T>) {
this.defaultMod = mods?.default ? mods.default : def
this.transformMod = mods?.transform ? mods.transform : (v: T) => v
constructor(mods?: NodeMods<T>, defaultMods?: NodeMods<T>, ) {
this.defaultMod = mods?.default ? mods.default :
defaultMods?.default ? defaultMods.default : (v) => v
this.transformMod = mods?.transform ? mods.transform :
defaultMods?.transform ? defaultMods.transform : (v) => v
}
setParent(parent: INode<any>) {
@@ -51,8 +56,8 @@ export abstract class AbstractNode<T> implements INode<T> {
updateModel(el: Element, path: Path, model: DataModel) {}
default(): T {
return this.defaultMod()
default(value?: T) {
return this.defaultMod(value)
}
transform(value: T) {

View File

@@ -10,7 +10,9 @@ export class BooleanNode extends AbstractNode<boolean> {
force: boolean
constructor(mods?: BooleanNodeMods) {
super(() => false, mods)
super(mods, {
default: () => false
})
this.force = (mods?.force === true)
}

View File

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

View File

@@ -8,7 +8,9 @@ export class ListNode extends AbstractNode<IObject[]> {
protected children: INode<any>
constructor(values: INode<any>, mods?: NodeMods<IObject[]>) {
super(() => [], mods)
super(mods, {
default: () => []
})
this.children = values
}

View File

@@ -1,5 +1,4 @@
import { AbstractNode, NodeMods, INode, StateNode } from './AbstractNode'
import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
import { Path } from '../model/Path'
import { IObject } from './ObjectNode'
@@ -13,7 +12,9 @@ export class MapNode extends AbstractNode<IMap> {
protected values: INode<any>
constructor(keys: StateNode<string>, values: INode<any>, mods?: NodeMods<IMap>) {
super(() => ({}), mods)
super(mods, {
default: () => ({})
})
this.keys = keys
this.values = values
}

View File

@@ -15,8 +15,9 @@ export class NumberNode extends AbstractNode<number> implements StateNode<number
max: number
constructor(mods?: NumberNodeMods) {
super(() => 0, mods)
console.log(mods)
super(mods, {
default: () => 0
})
this.integer = mods?.integer ? mods.integer : false
this.min = mods?.min !== undefined ? mods.min : -Infinity
this.max = mods?.max !== undefined ? mods.max : Infinity
@@ -25,7 +26,6 @@ export class NumberNode extends AbstractNode<number> implements StateNode<number
getState(el: Element) {
const value = el.querySelector('input')!.value
const parsed = this.integer ? parseInt(value) : parseFloat(value)
console.log(this.min)
if (parsed < this.min) return this.min
if (parsed > this.max) return this.max
return parsed

View File

@@ -10,7 +10,9 @@ export class ObjectNode extends AbstractNode<IObject> {
protected fields: NodeChildren
constructor(fields: NodeChildren, mods?: NodeMods<IObject>) {
super(() => ({}), mods)
super(mods, {
default: () => ({})
})
this.fields = fields
Object.values(fields).forEach(child => {
child.setParent(this)

View File

@@ -5,7 +5,7 @@ import { TreeView } from '../view/TreeView'
export class StringNode extends AbstractNode<string> implements StateNode<string> {
constructor(mods?: NodeMods<string>) {
super(() => '', mods)
super(mods)
}
getState(el: Element) {

View File

@@ -0,0 +1,101 @@
import { AbstractNode, NodeMods, RenderOptions, StateNode } from '../AbstractNode'
import { Path } from '../../model/Path'
import { DataModel } from '../../model/DataModel'
import { TreeView } from '../../view/TreeView'
export type IRange = number
| { min?: number, max?: number, type?: 'uniform' }
| { n?: number, p?: number, type: 'binomial' }
export interface RangeNodeMods extends NodeMods<IRange> {
integer?: boolean
}
export class RangeNode extends AbstractNode<IRange> implements StateNode<IRange> {
integer: boolean
constructor(mods?: RangeNodeMods) {
super(mods)
this.integer = mods?.integer ? mods.integer : false
}
parseNumber(str: string): number {
return this.integer ? parseInt(str) : parseFloat(str)
}
getState(el: Element): IRange {
const type = el.querySelector('select')!.value
if (type === 'exact') {
return this.parseNumber(el.querySelector('input')!.value)
}
if (type === 'range') {
const min = this.parseNumber(el.querySelectorAll('input')[0].value)
const max = this.parseNumber(el.querySelectorAll('input')[1].value)
return {
min: isNaN(min) ? undefined : min,
max: isNaN(max) ? undefined : max
}
}
const n = parseInt(el.querySelectorAll('input')[0].value)
const p = parseFloat(el.querySelectorAll('input')[1].value)
return {
type: 'binomial',
n: isNaN(n) ? undefined : n,
p: isNaN(p) ? undefined : p
}
}
updateModel(el: Element, path: Path, model: DataModel) {
model.set(path, this.getState(el))
}
renderRaw(path: Path, value: IRange, view: TreeView, options?: RenderOptions) {
let curType = ''
let input = ''
if (value === undefined || typeof value === 'number') {
curType = 'exact'
input = `<input value=${value === undefined ? '' : value}></input>`
} else if (value.type === 'binomial') {
curType = 'binomial'
input = `<label>n</label>
<input value=${value.n === undefined ? '' : value.n}></input>
<label>p</label>
<input value=${value.p === undefined ? '' : value.p}></input>`
} else {
curType = 'range'
input = `<label>Min</label>
<input value=${value.min === undefined ? '' : value.min}></input>
<label>Max</label>
<input value=${value.max === undefined ? '' : value.max}></input>`
}
const id = view.register(el => {
(el as HTMLInputElement).value = curType
el.addEventListener('change', evt => {
const target = (el as HTMLInputElement).value
const newValue = this.default(target === 'exact' ? undefined :
target === 'binomial' ? {type: 'binomial'} : {})
view.model.set(path, newValue)
evt.stopPropagation()
})
})
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
<select data-id="${id}">
<option value="exact">Exact</option>
<option value="range">Range</option>
<option value="binomial">Binomial</option>
</select>
${input}`
}
static isExact(v?: IRange) {
return v === undefined || typeof v === 'number'
}
static isRange(v?: IRange) {
return v !== undefined && typeof v !== 'number' && v.type !== 'binomial'
}
static isBinomial(v?: IRange) {
return v !== undefined && typeof v !== 'number' && v.type === 'binomial'
}
}

View File

@@ -31,15 +31,23 @@ export class TreeView implements ModelListener {
return id
}
registerClick(callback: (el: Element) => void): string {
registerEvent(type: string, callback: (el: Element) => void): string {
return this.register(el => {
el.addEventListener('click', evt => {
el.addEventListener(type, evt => {
callback(el)
evt.stopPropagation()
})
})
}
registerChange(callback: (el: Element) => void): string {
return this.registerEvent('change', callback)
}
registerClick(callback: (el: Element) => void): string {
return this.registerEvent('click', callback)
}
render() {
this.target.innerHTML = this.model.schema.render(new Path(), this.model.data, this)
for (const id in this.registry) {