|
|
|
|
@ -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,22 +62,37 @@ 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]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createElements (): Element[] {
|
|
|
|
|
// TODO: [C]
|
|
|
|
|
for ( const detachable of this.rerenderSubscriptions_ ) {
|
|
|
|
|
detachable.detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let renderables = this.render.call(this.instanceProxy);
|
|
|
|
|
|
|
|
|
|
@ -94,6 +122,8 @@ export abstract class Component {
|
|
|
|
|
throw new Error('cannot re-render a component with zero elements');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.onWillDetachFromDOM_();
|
|
|
|
|
|
|
|
|
|
// The reference element lets us know where to put new elements
|
|
|
|
|
const referenceElement = this.elements_[0];
|
|
|
|
|
|
|
|
|
|
@ -110,8 +140,36 @@ export abstract class Component {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createDynamicVAO_ (vaoName: string) {
|
|
|
|
|
this.vaos_[vaoName] = new ScalarVAO(null);
|
|
|
|
|
notifyWillDetachFromDOM () {
|
|
|
|
|
this.onWillDetachFromDOM_();
|
|
|
|
|
}
|
|
|
|
|
onWillDetachFromDOM_ () {
|
|
|
|
|
// Detach rerender subscriptions
|
|
|
|
|
for ( const detachable of this.rerenderSubscriptions_ ) {
|
|
|
|
|
detachable.detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Detach listeners from child renderables generated last time
|
|
|
|
|
if ( ! this.renderChildren_ ) {
|
|
|
|
|
throw new Error('tried to detach before rendering');
|
|
|
|
|
}
|
|
|
|
|
for ( const renderable of this.renderChildren_ ) {
|
|
|
|
|
renderable.notifyWillDetachFromDOM();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 +230,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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|