From c91e0a53006ffce64d59e6f14a058091f1e6e9e4 Mon Sep 17 00:00:00 2001 From: Misode Date: Sat, 30 May 2020 02:53:22 +0200 Subject: [PATCH] Add documentation --- src/Registries.ts | 22 +++++++++++++++ src/model/DataModel.ts | 27 ++++++++++++++++++ src/model/Path.ts | 26 +++++++++++++++++ src/nodes/AbstractNode.ts | 57 +++++++++++++++++++++++++++++++++++++- src/nodes/BooleanNode.ts | 6 ++++ src/nodes/EnumNode.ts | 7 +++++ src/nodes/ListNode.ts | 7 +++++ src/nodes/MapNode.ts | 9 ++++++ src/nodes/NumberNode.ts | 9 ++++++ src/nodes/ObjectNode.ts | 11 ++++++++ src/nodes/ReferenceNode.ts | 7 +++++ src/nodes/StringNode.ts | 7 +++++ src/view/SourceView.ts | 12 ++++++++ src/view/TreeView.ts | 35 +++++++++++++++++++++++ 14 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/Registries.ts b/src/Registries.ts index 32c27848..4d6eedf0 100644 --- a/src/Registries.ts +++ b/src/Registries.ts @@ -6,6 +6,9 @@ export interface Registry { get(id: string): T } +/** + * Registry for schemas + */ class SchemaRegistry implements Registry> { private registry: { [id: string]: INode } = {} @@ -22,6 +25,9 @@ class SchemaRegistry implements Registry> { } } +/** + * Registry for collections + */ class CollectionRegistry implements Registry { private registry: { [id: string]: string[] } = {} @@ -38,6 +44,9 @@ class CollectionRegistry implements Registry { } } +/** + * Registry for locales + */ export interface Locale { [key: string]: string } @@ -46,6 +55,11 @@ class LocaleRegistry implements Registry { private registry: { [id: string]: Locale } = {} language: string = '' + /** + * + * @param id locale identifier + * @param locale object mapping keys to translations + */ register(id: string, locale: Locale): void { this.registry[id] = locale } @@ -67,6 +81,14 @@ export const SCHEMAS = new SchemaRegistry() export const COLLECTIONS = new CollectionRegistry() export const LOCALES = new LocaleRegistry() +/** + * Gets the locale of a key from the locale registry. + * + * @param key string or path that refers to a locale ID. + * If a string is given, an exact match is required. + * If a path is given, it finds the longest match at the end. + * @returns undefined if the key isn't found for the selected language + */ export const locale = (key: string | Path) => { if (typeof key === 'string') { return LOCALES.getLocale(key) ?? key diff --git a/src/model/DataModel.ts b/src/model/DataModel.ts index e6cdabf0..2eec6acd 100644 --- a/src/model/DataModel.ts +++ b/src/model/DataModel.ts @@ -5,30 +5,52 @@ export interface ModelListener { invalidated(model: DataModel): void } +/** + * Holding the data linked to a given schema + */ export class DataModel { data: any schema: INode + /** A list of listeners that want to be notified when the model is invalidated */ listeners: ModelListener[] + /** + * @param schema node to use as schema for this model + */ constructor(schema: INode) { this.schema = schema this.data = schema.default() this.listeners = [] } + /** + * @param listener the listener to notify when the model is invalidated + */ addListener(listener: ModelListener) { this.listeners.push(listener) } + /** + * Force notify all listeners that the model is invalidated + */ invalidate() { this.listeners.forEach(listener => listener.invalidated(this)) } + /** + * Resets the full data and notifies listeners + * @param value new model data + */ reset(value: any) { this.data = value this.invalidate() } + /** + * Gets the data at a specified path + * @param path path at which to find the data + * @returns undefined, if the the path does not exist in the data + */ get(path: Path) { let node = this.data; for (let index of path) { @@ -38,6 +60,11 @@ export class DataModel { return node } + /** + * Updates the date on a path. Node will be removed when value is undefined + * @param path path to update + * @param value new data at the specified path + */ set(path: Path, value: any) { let node = this.data; for (let index of path.pop()) { diff --git a/src/model/Path.ts b/src/model/Path.ts index e192131a..dbe00bda 100644 --- a/src/model/Path.ts +++ b/src/model/Path.ts @@ -2,23 +2,41 @@ import { DataModel } from "./DataModel" export type PathElement = (string | number) +/** + * Immutable helper class to represent a path in data + * @implements {Iterable} + */ export class Path implements Iterable { private arr: PathElement[] model?: DataModel + /** + * @param arr Initial array of path elements. Empty if not given + * @param model Model attached to this path + */ constructor(arr?: PathElement[], model?: DataModel) { this.arr = arr ?? [] this.model = model } + /** + * The last element of this path + */ last(): PathElement { return this.arr[this.arr.length - 1] } + /** + * A new path with the last element removed + */ pop(): Path { return new Path(this.arr.slice(0, -1), this.model) } + /** + * A new path with an element added at the end + * @param element element to push at the end of the array + */ push(element: PathElement): Path { return new Path([...this.arr, element], this.model) } @@ -31,10 +49,18 @@ export class Path implements Iterable { return this.arr } + /** + * Attaches a model to this path and all paths created from this + * @param model + */ withModel(model: DataModel): Path { return new Path([...this.arr], model) } + /** + * Gets the data from the model if it was attached + * @returns undefined, if no model was attached + */ get(): any { return this.model?.get(this) } diff --git a/src/nodes/AbstractNode.ts b/src/nodes/AbstractNode.ts index 85b63ed2..6e5aca79 100644 --- a/src/nodes/AbstractNode.ts +++ b/src/nodes/AbstractNode.ts @@ -2,8 +2,11 @@ import { DataModel } from "../model/DataModel" import { Path } from "../model/Path" import { TreeView } from "../view/TreeView" +/** + * Schema node that supports some standard transformations + */ export interface INode { - default: IDefault + default: (value?: T) => T | undefined transform: (path: Path, value: T) => any enabled: (path: Path, model: DataModel) => boolean render: (path: Path, value: T, view: TreeView, options?: RenderOptions) => string @@ -36,12 +39,20 @@ export interface NodeMods { force?: IForce } +/** + * Basic implementation of the nodes + * + * h + */ export abstract class AbstractNode implements INode { defaultMod: IDefault transformMod: ITransform enableMod: IEnable forceMod: IForce + /** + * @param mods modifiers of the default transformations + */ constructor(mods?: NodeMods) { this.defaultMod = mods?.default ?? ((v) => v) this.transformMod = mods?.transform ?? ((v) => v) @@ -49,6 +60,12 @@ export abstract class AbstractNode implements INode { this.forceMod = mods?.force ?? (() => false) } + /** + * Runs when the element is mounted to the DOM + * @param el mounted element + * @param path data path that this node represents + * @param view corresponding tree view where this was mounted + */ mounted(el: Element, path: Path, view: TreeView) { el.addEventListener('change', evt => { this.updateModel(el, path, view.model) @@ -56,18 +73,37 @@ export abstract class AbstractNode implements INode { }) } + /** + * Runs when the DOM element 'change' event is called + * @param el mounted element + * @param path data path that this node represents + * @param model corresponding model + */ updateModel(el: Element, path: Path, model: DataModel) {} + /** + * The default value of this node + * @param value optional original value + */ default(value?: T) { return this.defaultMod(value) } + /** + * Transforms the data model to the final output format + * @param + */ transform(path: Path, value: T) { if (!this.enabled(path)) return undefined if (value === undefined && this.force()) value = this.default(value)! return this.transformMod(value) } + /** + * Determines whether the node should be enabled for this path + * @param path + * @param model + */ enabled(path: Path, model?: DataModel) { if (model) path = path.withModel(model) return this.enableMod(path.pop()) @@ -77,6 +113,14 @@ export abstract class AbstractNode implements INode { return this.forceMod() } + /** + * Wraps and renders the node + * @param path location + * @param value data used at + * @param view tree view context, containing the model + * @param options optional render options + * @returns string HTML wrapped representation of this node using the given data + */ render(path: Path, value: T, view: TreeView, options?: RenderOptions): string { if (!this.enabled(path, view.model)) return '' @@ -88,7 +132,18 @@ export abstract class AbstractNode implements INode { ` } + /** + * Renders the node and handles events to update the model + * @param path + * @param value + * @param view tree view context, containing the model + * @param options optional render options + * @returns string HTML representation of this node using the given data + */ abstract renderRaw(path: Path, value: T, view: TreeView, options?: RenderOptions): string + /** + * The CSS classname used in the wrapped
+ */ abstract getClassName(): string } diff --git a/src/nodes/BooleanNode.ts b/src/nodes/BooleanNode.ts index 5c24de4a..8101a57d 100644 --- a/src/nodes/BooleanNode.ts +++ b/src/nodes/BooleanNode.ts @@ -3,8 +3,14 @@ import { Path } from "../model/Path"; import { TreeView } from "../view/TreeView"; import { locale } from "../Registries"; +/** + * Boolean node with two buttons for true/false + */ export class BooleanNode extends AbstractNode { + /** + * @param mods optional node modifiers + */ constructor(mods?: NodeMods) { super({ default: () => false, diff --git a/src/nodes/EnumNode.ts b/src/nodes/EnumNode.ts index 712a38de..e249bc8b 100644 --- a/src/nodes/EnumNode.ts +++ b/src/nodes/EnumNode.ts @@ -4,10 +4,17 @@ import { TreeView } from '../view/TreeView' import { Path } from '../model/Path' import { locale } from '../Registries' +/** + * Enum node that shows a list of options to choose from + */ export class EnumNode extends AbstractNode implements StateNode { protected options: string[] static className = 'enum-node' + /** + * @param options options to choose from in the select + * @param mods optional node modifiers or a string to be the default value + */ constructor(options: string[], mods?: NodeMods | string) { super(typeof mods === 'string' ? { default: () => mods, diff --git a/src/nodes/ListNode.ts b/src/nodes/ListNode.ts index 9c7cd237..a15916d7 100644 --- a/src/nodes/ListNode.ts +++ b/src/nodes/ListNode.ts @@ -5,9 +5,16 @@ import { Path } from '../model/Path' import { IObject } from './ObjectNode' import { locale } from '../Registries' +/** + * List node where children can be added and removed from + */ export class ListNode extends AbstractNode { protected children: INode + /** + * @param values node used for its children + * @param mods optional node modifiers + */ constructor(values: INode, mods?: NodeMods) { super({ default: () => [], diff --git a/src/nodes/MapNode.ts b/src/nodes/MapNode.ts index 67d1bcd4..0ff9f645 100644 --- a/src/nodes/MapNode.ts +++ b/src/nodes/MapNode.ts @@ -8,10 +8,19 @@ export type IMap = { [name: string]: IObject } +/** + * Map nodes similar to list nodes, but a string key is required to add children + */ export class MapNode extends AbstractNode { protected keys: StateNode protected values: INode + /** + * + * @param keys node used for the string key + * @param values node used for the map values + * @param mods optional node modifiers + */ constructor(keys: StateNode, values: INode, mods?: NodeMods) { super({ default: () => ({}), diff --git a/src/nodes/NumberNode.ts b/src/nodes/NumberNode.ts index 14f4c906..d4e3e804 100644 --- a/src/nodes/NumberNode.ts +++ b/src/nodes/NumberNode.ts @@ -5,16 +5,25 @@ import { TreeView } from '../view/TreeView' import { locale } from '../Registries' export interface NumberNodeMods extends NodeMods { + /** Whether numbers should be converted to integers on input */ integer?: boolean + /** If specified, number will be capped at this minimum */ min?: number + /** If specified, number will be capped at this maximum */ max?: number } +/** + * Configurable number node with one text field + */ export class NumberNode extends AbstractNode implements StateNode { integer: boolean min: number max: number + /** + * @param mods optional node modifiers + */ constructor(mods?: NumberNodeMods) { super({ default: () => 0, diff --git a/src/nodes/ObjectNode.ts b/src/nodes/ObjectNode.ts index 71f7b0e8..fe2d3191 100644 --- a/src/nodes/ObjectNode.ts +++ b/src/nodes/ObjectNode.ts @@ -16,20 +16,31 @@ export type IObject = { export type FilteredChildren = { [name: string]: INode + /** The field to filter on */ [Switch]?: string + /** Map of filter values to node fields */ [Case]?: NestedNodeChildren } export interface ObjectNodeMods extends NodeMods { + /** Whether the object can be collapsed. Necessary when recursively nesting. */ collapse?: boolean } +/** + * Object node containing fields with different types. + * Has the ability to filter fields based on a switch field. + */ export class ObjectNode extends AbstractNode { fields: NodeChildren cases: NestedNodeChildren filter?: string collapse?: boolean + /** + * @param fields children containing the optional switch and case + * @param mods optional node modifiers + */ constructor(fields: FilteredChildren, mods?: ObjectNodeMods) { super({ default: () => ({}), diff --git a/src/nodes/ReferenceNode.ts b/src/nodes/ReferenceNode.ts index e0c33b31..3e4aadab 100644 --- a/src/nodes/ReferenceNode.ts +++ b/src/nodes/ReferenceNode.ts @@ -7,10 +7,17 @@ export interface AnyNodeMods extends NodeMods { [name: string]: any } +/** + * Reference node. Must be used when recursively adding nodes. + */ export class ReferenceNode extends AbstractNode { protected reference: () => INode options: RenderOptions + /** + * @param id schema id that was registered + * @param mods optional node modifiers + */ constructor(id: string, mods?: AnyNodeMods) { super(mods) this.options = { diff --git a/src/nodes/StringNode.ts b/src/nodes/StringNode.ts index 2575d7d9..3dd0aa2d 100644 --- a/src/nodes/StringNode.ts +++ b/src/nodes/StringNode.ts @@ -5,12 +5,19 @@ import { TreeView } from '../view/TreeView' import { locale } from '../Registries' export interface StringNodeMods extends NodeMods { + /** Whether the string can also be empty */ allowEmpty?: boolean } +/** + * Simple string node with one text field + */ export class StringNode extends AbstractNode implements StateNode { allowEmpty: boolean + /** + * @param mods optional node modifiers + */ constructor(mods?: StringNodeMods) { super(mods) this.allowEmpty = mods?.allowEmpty ?? false diff --git a/src/view/SourceView.ts b/src/view/SourceView.ts index 70970e14..85b90a02 100644 --- a/src/view/SourceView.ts +++ b/src/view/SourceView.ts @@ -1,10 +1,18 @@ import { DataModel, ModelListener } from "../model/DataModel" import { Path } from "../model/Path" +/** + * JSON representation view of the model. + * Renders the result in a