mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Add conditional rendering
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user