From ea86a7f3efd81944f97f7f48d94785506f26578d Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Fri, 24 Feb 2023 22:22:22 -0500 Subject: [PATCH] Add property definition feature --- src/component/Component.ts | 101 ++++++++++++++++++++++++++++++++++--- src/vao/VAO.ts | 4 ++ 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/component/Component.ts b/src/component/Component.ts index 370b295..0da718c 100644 --- a/src/component/Component.ts +++ b/src/component/Component.ts @@ -1,6 +1,6 @@ import { TAttributes } from "../html/htmldefs"; import { ScalarVAO } from "../vao/ScalarVAO.js"; -import { AbstractVAO, Detachable, VAO } from "../vao/VAO.js"; +import { AbstractVAO, Detachable, VAO, VAOConstructor } from "../vao/VAO.js"; import { Renderable, INotifyMounted } from "./componentdefs"; // === TODO === @@ -13,11 +13,24 @@ import { Renderable, INotifyMounted } from "./componentdefs"; // [F] investigate possibility of detecting/avoiding infinite recursion here // [G] definition of "mounted" doesn't match React. Maybe call it something // else to avoid confusion. +// [H] investigate possibility of using an AST test to ensure methods like +// setOrCreateVAO are called instead of re-implementing their behaviour +// [I] VAOs should install themselves into components so they can add behaviour +// to the instance proxy and implement optimizations for methods like +// Array.prototype.push, Object.assign, etc. + +interface PropertyDefinition { + $?: VAOConstructor, + factory?: () => any +} +type TPropertyDefinitions = { [key: string]: PropertyDefinition } export abstract class Component { public attributes: TAttributes public children: Renderable[] + static properties: TPropertyDefinitions | undefined; + protected renderChildren_: Renderable[] | null = null; protected instanceProxy_: Component | null = null; protected vaos_: { [name: string]: VAO } @@ -49,13 +62,31 @@ export abstract class Component { this.rerenderVAOs_ = {}; this.rerenderSubscriptions_ = []; + const constructorAsWhatItIs: typeof Component = + (this.constructor as unknown) as typeof Component; + const allProperties = constructorAsWhatItIs.allProperties_; + + for ( let k in allProperties ) { + const property = allProperties[k]; + const value = this.getPropertyInitialValue_(k); + + if ( property.$ !== undefined ) { + const vao = this.createVAOFromProperty_(property, value); + if ( ! ( vao instanceof AbstractVAO ) ) { + throw new Error('expected a VAO'); + } + // TODO: [I] + this.vaos_[k] = vao; + } else { + this.createDynamicVAO_(k, value) + } + } + for ( let k in attributes ) { if ( attributes[k] instanceof AbstractVAO ) { this.vaos_[k] = attributes[k]; } else { - // TODO: [E] - const vao = new ScalarVAO(attributes[k]); - this.vaos_[k] = vao; + this.setOrCreateVAO_(k, attributes[k]); } } } @@ -110,8 +141,18 @@ export abstract class Component { }) } - createDynamicVAO_ (vaoName: string) { - this.vaos_[vaoName] = new ScalarVAO(null); + createDynamicVAO_ (vaoName: string, value?: any) { + // TODO: [E], [I] + this.vaos_[vaoName] = new ScalarVAO(value ?? null); + } + + // TODO: [H] + setOrCreateVAO_ (vaoName: string, value: any) { + if ( ! this.vaos_[vaoName] ) { + this.createDynamicVAO_(vaoName, value); + return; + } + this.vaos_[vaoName].set(value); } notifyMounted(event: INotifyMounted) { @@ -172,4 +213,52 @@ export abstract class Component { return this.instanceProxy_; } + + static allProperties__: TPropertyDefinitions | null; + + static get allProperties_ () { + if ( this.allProperties__ ) return this.allProperties__; + + const properties: TPropertyDefinitions = {}; + let cls = this; + for ( + ; + cls.prototype instanceof Component; + cls = Object.getPrototypeOf(cls) + ) { + if ( ! cls.properties ) continue; + for ( const k in cls.properties ) { + if ( properties.hasOwnProperty(k) ) { + properties[k] = Object.assign( + { ...cls.properties[k] }, properties[k]); + } else { + properties[k] = cls.properties[k]; + } + } + } + + return this.allProperties__ = properties; + } + + getPropertyInitialValue_ (k: string): any { + const constructorAsWhatItIs: typeof Component = + (this.constructor as unknown) as typeof Component; + const allProperties = constructorAsWhatItIs.allProperties_; + + const property = allProperties[k]; + + if ( property.factory !== undefined ) { + return property.factory(); + } + + return null; + } + + createVAOFromProperty_ (property: PropertyDefinition, initialValue: any): VAO { + if ( property.$ === undefined ) { + throw new Error('called createVAOFromProperty_ with invalid input'); + } + + return new property.$(initialValue); + } } diff --git a/src/vao/VAO.ts b/src/vao/VAO.ts index 389d511..0550e62 100644 --- a/src/vao/VAO.ts +++ b/src/vao/VAO.ts @@ -25,6 +25,10 @@ export interface VAO { set (value: any): void } +export interface VAOConstructor { + new (initialValue: any): VAO +} + export abstract class AbstractVAO implements VAO { private listeners_: TListenerList constructor () {