Add filtered node + schema selector

This commit is contained in:
Misode
2020-05-27 01:09:15 +02:00
parent 549dea2738
commit 5db78d3ff1
6 changed files with 280 additions and 54 deletions
+2
View File
@@ -6,6 +6,8 @@
<title>Document</title>
</head>
<body>
<div id="header"></div>
<hr>
<div id="view"></div>
<hr>
<div id="source" style="font-family: monospace;"></div>
+22 -52
View File
@@ -1,61 +1,31 @@
import { RootNode } from '../nodes/RootNode'
import { EnumNode } from '../nodes/EnumNode'
import { ObjectNode } from '../nodes/ObjectNode'
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'
import { BooleanNode } from '../nodes/BooleanNode'
import { MapNode } from '../nodes/MapNode'
import { NumberNode } from '../nodes/NumberNode'
import { RangeNode } from '../nodes/custom/RangeNode'
import { ResourceNode } from '../nodes/custom/ResourceNode'
import { PredicateSchema } from '../schemas/PredicateSchema'
import { SandboxSchema } from '../schemas/SandboxSchema'
const EntityCollection = ['sheep', 'pig']
const predicateModel = new DataModel(PredicateSchema)
const sandboxModel = new DataModel(SandboxSchema)
const predicateTree = new RootNode('predicate', {
condition: new EnumNode(['foo', 'bar'], {
default: () => 'bar',
transform: (s: string) => (s === 'foo') ? {test: 'baz'} : s
}),
number: new NumberNode({integer: false, min: 0}),
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({force: () => true}),
recipes: new MapNode(
new StringNode(),
new RangeNode({
default: (v) => RangeNode.isExact(v) ? 2 : v
})
)
}),
effects: new ListNode(
new ObjectNode({
type: new EnumNode(EntityCollection),
nbt: new StringNode()
}, {
default: () => ({
type: 'sheep'
})
})
)
}, {
default: () => ({
condition: 'foo',
predicate: {
nbt: 'hi'
}
})
});
let model = predicateModel
const model = new DataModel(predicateTree)
const modelSelector = document.createElement('select')
modelSelector.value = 'predicate'
modelSelector.innerHTML = `
<option value="predicate">Predicate</option>
<option value="sandbox">Sandbox</option>`
modelSelector.addEventListener('change', evt => {
console.log("hello?")
if (modelSelector.value === 'sandbox') {
model = sandboxModel
} else {
model = predicateModel
}
new TreeView(model, document!.getElementById('view')!)
new SourceView(model, document!.getElementById('source')!)
model.invalidate()
})
document.getElementById('header')?.append(modelSelector)
new TreeView(model, document!.getElementById('view')!)
new SourceView(model, document!.getElementById('source')!)
+3 -2
View File
@@ -1,5 +1,6 @@
import { RootNode } from "../nodes/RootNode"
import { Path } from "./Path"
import { INode } from "../nodes/AbstractNode"
export interface ModelListener {
invalidated(model: DataModel): void
@@ -7,10 +8,10 @@ export interface ModelListener {
export class DataModel {
data: any
schema: RootNode
schema: INode<any>
listeners: ModelListener[]
constructor(schema: RootNode) {
constructor(schema: INode<any>) {
this.schema = schema
this.data = schema.default()
this.listeners = []
+53
View File
@@ -0,0 +1,53 @@
import { NodeMods, INode, NodeChildren, AbstractNode, RenderOptions } from './AbstractNode'
import { IObject } from './ObjectNode'
import { Path } from '../model/Path'
import { TreeView } from '../view/TreeView'
export const Switch = Symbol('switch')
type NestedNodeChildren = {
[name: string]: NodeChildren
}
export type FilteredChildren = {
[name: string]: INode<any>
[Switch]: NestedNodeChildren
}
export class FilteredNode extends AbstractNode<IObject> {
filter: string
fields: NodeChildren
cases: NestedNodeChildren
constructor(filter: string, fields: FilteredChildren, mods?: NodeMods<IObject>) {
super(mods)
this.filter = filter;
const {[Switch]: _switch, ..._fields} = fields
this.fields = _fields
this.cases = _switch
}
transform(path: Path, value: IObject) {
if (value === undefined) return undefined
value = value ?? {}
const activeCase = this.cases[value[this.filter]] ?? []
const activeFields = {...this.fields, ...activeCase}
let res: any = {}
Object.keys(activeFields).forEach(f =>
res[f] = activeFields[f].transform(path.push(f), value[f])
)
return res;
}
renderRaw(path: Path, value: IObject, view: TreeView, options?: RenderOptions) {
if (value === undefined) return ``
const activeCase = this.cases[value[this.filter]] ?? []
const activeFields = {...this.fields, ...activeCase}
return `${options?.hideLabel ? `` : `<label>${path.last()}:</label>
<div style="padding-left:8px">`}
${Object.keys(activeFields).map(f => {
return activeFields[f].render(path.push(f), value[f], view)
}).join('')}
${options?.hideLabel ? `` : `</div>`}`
}
}
+147
View File
@@ -0,0 +1,147 @@
import { EnumNode } from '../nodes/EnumNode';
import { ResourceNode } from '../nodes/custom/ResourceNode';
import { NumberNode } from '../nodes/NumberNode';
import { BooleanNode } from '../nodes/BooleanNode';
import { FilteredNode, Switch } from '../nodes/FilteredNode';
import { ListNode } from '../nodes/ListNode';
import { RangeNode } from '../nodes/custom/RangeNode';
import { MapNode } from '../nodes/MapNode';
import { StringNode } from '../nodes/StringNode';
import { INode } from '../nodes/AbstractNode';
const conditions = [
'alternative',
'requirements',
'inverted',
'reference',
'entity_properties',
'block_state_property',
'match_tool',
'damage_source_properties',
'location_check',
'weather_check',
'time_check',
'entity_scores',
'random_chance',
'random_chance_with_looting',
'table_bonus',
'killed_by_player',
'survives_explosion'
]
const enchantments = [
'aqua_affinity',
'bane_of_arthropods',
'blast_protection',
'channeling',
'binding_curse',
'vanishing_curse',
'depth_strider',
'efficiency',
'feather_falling',
'fire_aspect',
'fire_protection',
'flame',
'fortune',
'frost_walker',
'impaling',
'infinity',
'knockback',
'looting',
'loyalty',
'luck_of_the_sea',
'lure',
'mending',
'multishot',
'piercing',
'power',
'projectile_protection',
'protection',
'punch',
'quick_charge',
'respiration',
'riptide',
'sharpness',
'silk_touch',
'smite',
'sweeping',
'thorns',
'unbreaking'
]
const entitySources = ['this', 'killer', 'killer_player']
export let PredicateSchema: FilteredNode
PredicateSchema = new FilteredNode('condition', {
condition: new EnumNode(conditions, 'random_chance'),
[Switch]: {
'alternative': {
// terms: new ListNode(PredicateSchema()),
},
'block_state_property': {
block: new ResourceNode(),
properties: new MapNode(
new StringNode(),
new StringNode()
),
},
'damage_source_properties': {
// predicate: DamageSchema,
},
'entity_properties': {
entity: new EnumNode(entitySources, 'this'),
// predicate: EntitySchema,
},
'entity_scores': {
entity: new EnumNode(entitySources, 'this'),
scores: new MapNode(
new StringNode(),
new RangeNode()
)
},
'inverted': {
// term: PredicateSchema,
},
'killed_by_player': {
inverse: new BooleanNode()
},
'location_check': {
offsetX: new NumberNode({integer: true}),
offsetY: new NumberNode({integer: true}),
offsetZ: new NumberNode({integer: true}),
// predicate: LocationSchema,
},
'match_tool': {
// predicate: ItemSchema,
},
'random_chance': {
chance: new NumberNode({min: 0, max: 1}),
},
'random_chance_with_looting': {
chance: new NumberNode({min: 0, max: 1}),
looting_multiplier: new NumberNode(),
},
'reference': {
name: new StringNode(),
},
'table_bonus': {
enchantment: new EnumNode(enchantments),
chances: new ListNode(
new NumberNode({min: 0, max: 1})
),
},
'time_check': {
value: new RangeNode(),
period: new NumberNode(),
},
'weather_check': {
raining: new BooleanNode(),
thrundering: new BooleanNode(),
}
}
}, {
default: () => ({
condition: 'random_chance',
chance: 0.5
})
})
+53
View File
@@ -0,0 +1,53 @@
import { ObjectNode } from '../nodes/ObjectNode';
import { EnumNode } from '../nodes/EnumNode';
import { ResourceNode } from '../nodes/custom/ResourceNode';
import { NumberNode } from '../nodes/NumberNode';
import { BooleanNode } from '../nodes/BooleanNode';
import { RootNode } from '../nodes/RootNode';
import { RangeNode } from '../nodes/custom/RangeNode';
import { MapNode } from '../nodes/MapNode';
import { StringNode } from '../nodes/StringNode';
import { ListNode } from '../nodes/ListNode';
const EntityCollection = ['sheep', 'pig']
export const SandboxSchema = new RootNode('predicate', {
condition: new EnumNode(['foo', 'bar'], {
default: () => 'bar',
transform: (s: string) => (s === 'foo') ? {test: 'baz'} : s
}),
number: new NumberNode({integer: false, min: 0}),
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({force: () => true}),
recipes: new MapNode(
new StringNode(),
new RangeNode({
default: (v) => RangeNode.isExact(v) ? 2 : v
})
)
}),
effects: new ListNode(
new ObjectNode({
type: new EnumNode(EntityCollection),
nbt: new StringNode()
}, {
default: () => ({
type: 'sheep'
})
})
)
}, {
default: () => ({
condition: 'foo',
predicate: {
nbt: 'hi'
}
})
});