mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Add range node + default mods
This commit is contained in:
@@ -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
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -21,7 +21,6 @@ export class DataModel {
|
||||
}
|
||||
|
||||
invalidate() {
|
||||
console.log(this.data)
|
||||
this.listeners.forEach(listener => listener.invalidated(this))
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
101
src/nodes/custom/RangeNode.ts
Normal file
101
src/nodes/custom/RangeNode.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user