mirror of
https://github.com/misode/misode.github.io.git
synced 2026-04-23 07:10:41 +00:00
Add view classes
This commit is contained in:
@@ -3,7 +3,8 @@ 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'
|
||||
|
||||
const EntityCollection = ['sheep', 'pig']
|
||||
|
||||
@@ -12,7 +13,7 @@ const predicateTree = new RootNode('predicate', {
|
||||
transform: (s: string) => (s === 'foo') ? {test: 'baz'} : s
|
||||
}),
|
||||
predicate: new ObjectNode({
|
||||
type: new EnumNode(EntityCollection, {}),
|
||||
type: new EnumNode(EntityCollection),
|
||||
nbt: new StringNode()
|
||||
})
|
||||
}, {
|
||||
@@ -20,7 +21,8 @@ const predicateTree = new RootNode('predicate', {
|
||||
});
|
||||
|
||||
const model = new DataModel(predicateTree)
|
||||
const treeView = new TreeView(model)
|
||||
const sourceView = new SourceView(model)
|
||||
|
||||
model.render(document!.getElementById('view')!)
|
||||
|
||||
// document!.getElementById('source')!.textContent = predicateTree.transform(dummyData)
|
||||
treeView.render(document!.getElementById('view')!)
|
||||
sourceView.render(document!.getElementById('source')!)
|
||||
|
||||
@@ -1,38 +1,12 @@
|
||||
import { RootNode } from "../nodes/RootNode"
|
||||
import { Path } from "./Path"
|
||||
|
||||
type Registry = {
|
||||
[id: string]: (el: Element) => void
|
||||
}
|
||||
|
||||
const registryIdLength = 12
|
||||
const dec2hex = (dec: number) => ('0' + dec.toString(16)).substr(-2)
|
||||
|
||||
export function getId() {
|
||||
var arr = new Uint8Array((registryIdLength || 40) / 2)
|
||||
window.crypto.getRandomValues(arr)
|
||||
return Array.from(arr, dec2hex).join('')
|
||||
}
|
||||
|
||||
export class DataModel {
|
||||
private data: any
|
||||
private schema: RootNode
|
||||
private registry: Registry = {}
|
||||
data: any
|
||||
schema: RootNode
|
||||
|
||||
constructor(schema: RootNode) {
|
||||
this.schema = schema
|
||||
this.data = schema.default
|
||||
}
|
||||
|
||||
register(id: string, callback: (el: Element) => void) {
|
||||
this.registry[id] = callback
|
||||
}
|
||||
|
||||
render(target: HTMLElement) {
|
||||
target.innerHTML = this.schema.render(new Path(), this.data, this)
|
||||
for (const id in this.registry) {
|
||||
const element = target.querySelector(`[data-id="${id}"]`)
|
||||
if (element !== null) this.registry[id](element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { DataModel, getId } from "../model/DataModel"
|
||||
import { DataModel } from "../model/DataModel"
|
||||
import { Path } from "../model/Path"
|
||||
import { TreeView } from "../view/TreeView"
|
||||
|
||||
export interface INode<T> {
|
||||
setParent: (parent: INode<any>) => void
|
||||
transform: (value: T) => any
|
||||
render: (path: Path, value: T, model: DataModel) => string
|
||||
render: (path: Path, value: T, view: TreeView) => string
|
||||
}
|
||||
|
||||
export type NodeChildren = {
|
||||
@@ -30,17 +31,16 @@ export abstract class AbstractNode<T> implements INode<T> {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
wrap(path: Path, model: DataModel, renderResult: string): string {
|
||||
const id = getId()
|
||||
model.register(id, el => {
|
||||
wrap(path: Path, view: TreeView, renderResult: string): string {
|
||||
const id = view.register(el => {
|
||||
console.log(`Callback ${id} -> ${path.last()}`)
|
||||
this.mounted(el, path, model)
|
||||
this.mounted(el, path, view)
|
||||
})
|
||||
return `<div data-id="${id}">${renderResult}</div>`
|
||||
}
|
||||
|
||||
mounted(el: Element, path: Path, model: DataModel) {
|
||||
el.addEventListener('change', evt => this.updateModel(el, path, model))
|
||||
mounted(el: Element, path: Path, view: TreeView) {
|
||||
el.addEventListener('change', evt => this.updateModel(el, path, view.model))
|
||||
}
|
||||
|
||||
updateModel(el: Element, path: Path, model: DataModel) {}
|
||||
@@ -49,5 +49,5 @@ export abstract class AbstractNode<T> implements INode<T> {
|
||||
return this.transformMod(value)
|
||||
}
|
||||
|
||||
abstract render(path: Path, value: T, model: DataModel): string
|
||||
abstract render(path: Path, value: T, view: TreeView): string
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AbstractNode, NodeMods } from './AbstractNode'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { Path } from '../model/Path'
|
||||
|
||||
export class EnumNode extends AbstractNode<string> {
|
||||
@@ -13,8 +14,8 @@ export class EnumNode extends AbstractNode<string> {
|
||||
updateModel(el: Element, path: Path, model: DataModel) {
|
||||
}
|
||||
|
||||
render(path: Path, value: string, model: DataModel) {
|
||||
return this.wrap(path, model, `<span>${path.last()}</span>
|
||||
render(path: Path, value: string, view: TreeView) {
|
||||
return this.wrap(path, view, `<span>${path.last()}</span>
|
||||
<select value="${value}">
|
||||
${this.options.map(o => `<option>${o}</option>`)}
|
||||
</select>`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AbstractNode, NodeChildren, NodeMods } from './AbstractNode'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { Path } from '../model/Path'
|
||||
|
||||
export interface IObject {
|
||||
@@ -27,11 +27,11 @@ export class ObjectNode extends AbstractNode<IObject> {
|
||||
return res;
|
||||
}
|
||||
|
||||
render(path: Path, value: IObject, model: DataModel) {
|
||||
render(path: Path, value: IObject, view: TreeView) {
|
||||
if (value === undefined) return ``
|
||||
return this.wrap(path, model, `<span>${path.last()}:</span><div style="padding-left:8px">
|
||||
return this.wrap(path, view, `<span>${path.last()}:</span><div style="padding-left:8px">
|
||||
${Object.keys(this.fields).map(f => {
|
||||
return this.fields[f].render(path.push(f), value[f], model)
|
||||
return this.fields[f].render(path.push(f), value[f], view)
|
||||
}).join('')}
|
||||
</div>`)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NodeChildren, NodeMods } from './AbstractNode'
|
||||
import { ObjectNode, IObject } from './ObjectNode'
|
||||
import { Path } from '../model/Path'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
|
||||
export class RootNode extends ObjectNode {
|
||||
id: string
|
||||
@@ -15,11 +15,11 @@ export class RootNode extends ObjectNode {
|
||||
return JSON.stringify(super.transform(value))
|
||||
}
|
||||
|
||||
render(path: Path, value: IObject, model: DataModel) {
|
||||
render(path: Path, value: IObject, view: TreeView) {
|
||||
value = value || {}
|
||||
return `<div>
|
||||
${Object.keys(this.fields).map(f => {
|
||||
return this.fields[f].render(path.push(f), value[f], model)
|
||||
return this.fields[f].render(path.push(f), value[f], view)
|
||||
}).join('')}
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AbstractNode, NodeMods } from './AbstractNode'
|
||||
import { Path } from '../model/Path'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
|
||||
export class StringNode extends AbstractNode<string> {
|
||||
constructor(mods?: NodeMods<string>) {
|
||||
@@ -10,8 +11,8 @@ export class StringNode extends AbstractNode<string> {
|
||||
updateModel(el: Element, path: Path, model: DataModel) {
|
||||
}
|
||||
|
||||
render(path: Path, value: string, model: DataModel) {
|
||||
return this.wrap(path, model,
|
||||
render(path: Path, value: string, view: TreeView) {
|
||||
return this.wrap(path, view,
|
||||
`<span>${path.last()}</span> <input value="${value || ''}"></input>`)
|
||||
}
|
||||
}
|
||||
|
||||
14
src/view/SourceView.ts
Normal file
14
src/view/SourceView.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DataModel } from "../model/DataModel"
|
||||
import { Path } from "../model/Path"
|
||||
|
||||
export class SourceView {
|
||||
model: DataModel
|
||||
|
||||
constructor(model: DataModel) {
|
||||
this.model = model
|
||||
}
|
||||
|
||||
render(target: HTMLElement) {
|
||||
target.textContent = this.model.schema.transform(this.model.data)
|
||||
}
|
||||
}
|
||||
38
src/view/TreeView.ts
Normal file
38
src/view/TreeView.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DataModel } from "../model/DataModel"
|
||||
import { Path } from "../model/Path"
|
||||
|
||||
type Registry = {
|
||||
[id: string]: (el: Element) => void
|
||||
}
|
||||
|
||||
const registryIdLength = 12
|
||||
const dec2hex = (dec: number) => ('0' + dec.toString(16)).substr(-2)
|
||||
|
||||
export function getId() {
|
||||
var arr = new Uint8Array((registryIdLength || 40) / 2)
|
||||
window.crypto.getRandomValues(arr)
|
||||
return Array.from(arr, dec2hex).join('')
|
||||
}
|
||||
|
||||
export class TreeView {
|
||||
model: DataModel
|
||||
registry: Registry = {}
|
||||
|
||||
constructor(model: DataModel) {
|
||||
this.model = model
|
||||
}
|
||||
|
||||
register(callback: (el: Element) => void): string {
|
||||
const id = getId()
|
||||
this.registry[id] = callback
|
||||
return id
|
||||
}
|
||||
|
||||
render(target: HTMLElement) {
|
||||
target.innerHTML = this.model.schema.render(new Path(), this.model.data, this)
|
||||
for (const id in this.registry) {
|
||||
const element = target.querySelector(`[data-id="${id}"]`)
|
||||
if (element !== null) this.registry[id](element)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user