import { MathUtils } from '@uino/base-thing';
import { Utils } from '../common/Utils';
const __ = {
private: Symbol('private'),
}
let _defaultParams = {};
/**
* @class BaseComponentGroup
* The base component group.
* @memberof THING
* @public
*/
class BaseComponentGroup {
/**
* The base component group to manage components, you should inherit from it when you want to manage multiple components.
*/
constructor() {
this[__.private] = {};
let _private = this[__.private];
// Components
_private.components = new Map();
// Quick components accessor info
_private.componentsAccessorInfo = {};
// Quick components for some specified actions
_private.tickableComponents = null;
_private.renderableComponents = null;
_private.resizableComponents = null;
_private.refreshableComponents = null;
_private.loadableComponents = null;
_private.unloadableComponents = null;
_private.visibleComponents = null;
_private.parentChangeComponents = null;
_private.componentRegisterOrder = 0;
}
// #region Private Functions
_linkProperties(classType, componentName) {
let exportProperties = classType.exportProperties;
if (exportProperties) {
exportProperties.forEach(propertyName => {
Object.defineProperty(this, propertyName, {
enumerable: false,
configurable: true,
set(value) {
let component = this[componentName];
component[propertyName] = value;
},
get() {
let component = this[componentName];
return component[propertyName];
}
});
});
}
}
_linkFunctions(classType, componentName) {
let exportFunctions = classType.exportFunctions;
if (exportFunctions) {
exportFunctions.forEach(funcName => {
this[funcName] = function () {
let component = this[componentName];
return component[funcName].apply(component, arguments);
}
});
}
}
_linkComponent(classType, componentName, args = _defaultParams) {
let autoRegister = Utils.parseValue(args['autoRegister'], true);
if (autoRegister) {
this._linkProperties(classType, componentName);
this._linkFunctions(classType, componentName);
}
}
_unlinkComponent(classType) {
// Get properties from component to register into self object
let exportProperties = classType.exportProperties;
if (exportProperties) {
exportProperties.forEach(propertyName => {
delete this[propertyName];
});
}
// Get functions from component to register into self object
let exportFunctions = classType.exportFunctions;
if (exportFunctions) {
exportFunctions.forEach(funcName => {
delete this[funcName];
});
}
}
_setComponent(component, name, args) {
if (!component) {
return null;
}
let _private = this[__.private];
// Add component (we need to add it to set first, prevent infinity-loop in onAdd() interface of BaseCompnent)
_private.components.set(name, component);
// Notify added component event
this.onAddComponent(component, args);
// Update tickable components list
if (component.onUpdate) {
_private.tickableComponents = _private.tickableComponents || [];
_private.tickableComponents.push(component);
}
// Update renderable components list
if (component.onRender) {
_private.renderableComponents = _private.renderableComponents || [];
_private.renderableComponents.push(component);
}
// Update resizable components list
if (component.onResize) {
_private.resizableComponents = _private.resizableComponents || [];
_private.resizableComponents.push(component);
// Resize event should call it now
let object = this.object;
if (object) {
if (object.isApp) {
component.onResize(object.size[0], object.size[1]);
}
else {
component.onResize(object.app.size[0], object.app.size[1]);
}
}
}
// Update refresh components list
if (component.onRefresh) {
_private.refreshableComponents = _private.refreshableComponents || [];
_private.refreshableComponents.push(component);
}
// Update loadable components list
if (component.onLoadResource) {
_private.loadableComponents = _private.loadableComponents || [];
_private.loadableComponents.push(component);
}
// Update unloadable components list
if (component.onUnloadResource) {
_private.unloadableComponents = _private.unloadableComponents || [];
_private.unloadableComponents.push(component);
}
// Update visible components list
if (component.onVisibleChange) {
_private.visibleComponents = _private.visibleComponents || [];
_private.visibleComponents.push(component);
}
// Update parent change components list
if (component.onParentChange) {
_private.parentChangeComponents = _private.parentChangeComponents || [];
_private.parentChangeComponents.push(component);
}
return component;
}
_getComponentFromAccessor(classType, name, args) {
let _private = this[__.private];
let component = _private.components.get(name);
if (!component) {
component = args ? new classType(args) : new classType();
this._setComponent(component, name, args);
}
// Replace component to speed up accessor
delete this[name];
this[name] = component;
// Mark component as initialized
let accessorinfo = _private.componentsAccessorInfo[name];
accessorinfo.hasInstance = true;
accessorinfo.classType = classType;
return component;
}
_createAccessor(classType, name, args) {
return {
enumerable: false,
configurable: true,
get: () => {
return this._getComponentFromAccessor(classType, name, args);
}
}
}
_linkAccessor(classType, name, args) {
let _private = this[__.private];
// Create accessor
let accessor = this._createAccessor(classType, name, args);
// Build accessor info
let accessorInfo = {
classType,
accessor,
order: _private.componentRegisterOrder++
};
// Save constructor arguments
if (args && !args['isTemporary']) {
accessorInfo.args = Object.assign({}, args);
// Check whether it's resident component
accessorInfo.isResident = Utils.parseValue(args['isResident'], false);
}
// Update accessor info
_private.componentsAccessorInfo[name] = accessorInfo;
// Update object's component accessor
Object.defineProperty(this, name, accessor);
}
_unlinkAccessor(name) {
let _private = this[__.private];
delete this[name];
// Remove component accessor with name
delete _private.componentsAccessorInfo[name];
}
_removeComponent(name, unlink = true) {
let _private = this[__.private];
// Get existing component by name
let component = _private.components.get(name);
if (!component) {
return false;
}
// Notify component will be removed
this.onRemoveComponent(component);
// Remove component
_private.components.delete(name);
// Remove from list
Utils.removeFromArray(_private.tickableComponents, component);
Utils.removeFromArray(_private.renderableComponents, component);
Utils.removeFromArray(_private.resizableComponents, component);
Utils.removeFromArray(_private.refreshableComponents, component);
Utils.removeFromArray(_private.loadableComponents, component);
Utils.removeFromArray(_private.unloadableComponents, component);
Utils.removeFromArray(_private.visibleComponents, component);
Utils.removeFromArray(_private.parentChangeComponents, component);
// Unlink accessor
if (unlink) {
this._unlinkAccessor(name);
}
return true;
}
_registerComponent(classType, name, args) {
// Remove existing component
if (this.hasComponent(name)) {
this.removeComponent(name);
}
// Link component
this._linkAccessor(classType, name, args);
this._linkComponent(classType, name, args);
// Check whether auto create component
if (classType.isInstancedComponent) {
if (!this._getComponentFromAccessor(classType, name, args)) {
return false;
}
}
return true;
}
_unregisterComponent(accessor, name) {
if (this._removeComponent(name)) {
if (accessor.hasInstance) {
this._unlinkComponent(accessor.classType);
}
}
else {
this._unlinkAccessor(name);
}
}
_importComponentExternalData(name, external, options) {
if (external) {
let onGetComponentExternalData = options['onGetComponentExternalData'];
if (onGetComponentExternalData) {
external = onGetComponentExternalData(this, name, external);
if (!external) {
return;
}
}
let component = this[name];
if (component.onImport) {
component.onImport(external, options);
}
}
}
// #endregion
// #region Overrides
onAddComponent(component, args) {
component.onAdd(this, args);
}
onRemoveComponent(component) {
component.onRemove();
}
// #endregion
/**
* Check whether has registered component.
* @param {String} name The name.
* @returns {Boolean}
* @private
*/
hasRegisteredComponent(name) {
let _private = this[__.private];
let accessor = _private.componentsAccessorInfo[name];
if (!accessor) {
return false;
}
return true;
}
/**
* Get the accessor info of components.
* @returns {Object}
* @private
*/
getAccessorInfo() {
let _private = this[__.private];
return _private.componentsAccessorInfo;
}
clearAccessorInfo() {
let _private = this[__.private];
_private.componentsAccessorInfo = {};
}
/**
* Add component.
* @param {THING.BaseComponent|Object} component The component class or component object.
* @param {String} name The name.
* @param {Object} args? The initial arguments to create component.
* @returns {Boolean}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'myComponent');
* // @expect(obj.myComponent != null)
*/
addComponent(component, name, args) {
if (!component) {
return false;
}
if (!Utils.isString(name)) {
// Replace component args
if (Utils.isObject(name)) {
args = name;
}
// Generate unique name if not provide
name = MathUtils.generateUUID();
}
if (Utils.isObject(component)) {
// Remove the previous component if we provide name
if (name) {
this._removeComponent(name, false);
}
// Bind component
this._setComponent(component, name, args);
// Link component
this._linkAccessor(component.constructor, name, args);
this._linkComponent(component.constructor, name, args);
}
else {
// Bind component
this._registerComponent(component, name, args);
}
return true;
}
/**
* Remove component.
* @param {String} name The name.
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'myComponent');
* obj.removeComponent('myComponent');
* // @expect(obj.components.size == 0)
*/
removeComponent(name) {
let _private = this[__.private];
let accessorInfo = _private.componentsAccessorInfo[name];
this._removeComponent(name);
if (accessorInfo) {
this._linkAccessor(accessorInfo.classType, name, accessorInfo.args);
}
this._unregisterComponent(accessorInfo, name);
}
/**
* Remove all components.
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'component1');
* obj.addComponent(new THING.BaseComponent(), 'component2');
* obj.removeAllComponents();
* // @expect(obj.components.size == 0)
*/
removeAllComponents(force = false) {
let _private = this[__.private];
// Get all component names.
let componentNames = [..._private.components.keys()];
// Remove none resident components first
componentNames.sort((a, b) => {
let a1 = _private.componentsAccessorInfo[a];
let b1 = _private.componentsAccessorInfo[b];
if (a1.isResident && !b1.isResident) {
return 1;
}
if (b1.isResident && !a1.isResident) {
return -1;
}
return b1.order - a1.order;
});
// Remove all components
componentNames.forEach(name => {
// Skip for resident component
let accessorInfo = _private.componentsAccessorInfo[name];
if (!force && accessorInfo.isResident) {
return;
}
this.removeComponent(name);
});
}
/**
* The function to call when traverse component by type.
* @callback TraverseComponentByTypeCallback
* @param {THING.BaseComponent} component The component.
* @param {String} name The component name.
*/
/**
* Traverse component by type.
* @param {*} type The component type.
* @param {TraverseComponentByTypeCallback} callback The callback function.
* @public
* @example
* object.addComponent(new MyComponent(), 'myComponent');
* object.traverseComponentByType(MyComponent, (component, name) => {
* console.log(component, name);
* });
*/
traverseComponentByType(type, callback) {
let _private = this[__.private];
let accessors = _private.componentsAccessorInfo;
for (let key in accessors) {
let component = this[key];
if (!component) {
continue;
}
if (component instanceof type) {
callback(this[key], key);
}
}
}
/**
* Get component by name.
* @param {String} name The name.
* @returns {THING.BaseComponent}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'myComponent');
* let component = obj.getComponentByName('myComponent');
* // @expect(component != null)
*/
getComponentByName(name) {
let _private = this[__.private];
let component = _private.components.get(name);
if (component) {
return component;
}
let accessorInfo = _private.componentsAccessorInfo[name];
if (accessorInfo) {
return accessorInfo.accessor.get();
}
return null;
}
/**
* Get component by type.
* @param {*} type The component type.
* @returns {THING.BaseComponent}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'myComponent');
* let component = obj.getComponentByType(THING.BaseComponent);
* // @expect(component != null)
*/
getComponentByType(type) {
let _private = this[__.private];
let accessors = _private.componentsAccessorInfo;
for (let key in accessors) {
let component = this[key];
if (!component) {
continue;
}
if (component instanceof type) {
return component;
}
}
return null;
}
/**
* Get components by type.
* @param {*} type The component type.
* @returns {Array<THING.BaseComponent>}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'component1');
* obj.addComponent(new THING.BaseComponent(), 'component2');
* let components = obj.getComponentsByType(THING.BaseComponent);
* // @expect(components.length == 2)
*/
getComponentsByType(type) {
let components = [];
this.traverseComponentByType(type, (component) => {
components.push(component);
});
return components;
}
/**
* Get all components(it would create all registered components).
* @returns {Array<THING.BaseComponent>}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'component1');
* obj.addComponent(new THING.BaseComponent(), 'component2');
* let components = obj.getAllComponents();
* // @expect(components.length == 2)
*/
getAllComponents() {
let _private = this[__.private];
let components = [];
let accessors = _private.componentsAccessorInfo;
for (let key in accessors) {
let component = this.getComponentByName(key);
if (!component) {
continue;
}
components.push(component);
}
return components;
}
/**
* Check whether has component.
* @param {String} name The name.
* @returns {Boolean}
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'myComponent');
* let ret = obj.hasComponent('myComponent')
* // @expect(ret == true)
*/
hasComponent(name) {
let _private = this[__.private];
return _private.components.has(name);
}
/**
* Check whether has component instance.
* @param {String} name The name.
* @returns {Boolean}
* @private
*/
hasComponentInstance(name) {
let _private = this[__.private];
let accessorInfo = _private.componentsAccessorInfo[name];
if (!accessorInfo) {
return false;
}
return accessorInfo.hasInstance;
}
/**
* Check whether it's resident component.
* @param {String} name The name.
* @returns {Boolean}
* @private
*/
isResidentComponent(name) {
let _private = this[__.private];
let accessorInfo = _private.componentsAccessorInfo[name];
if (!accessorInfo) {
return false;
}
return !!accessorInfo.isResident;
}
onImportComponents(components, options = {}) {
let onGetObjectClassType = options['onGetObjectClassType'];
let onError = options['onError'];
// Import all components
components.forEach(componentData => {
let name = componentData.name;
let external = componentData.external || componentData.params || {};
let component = this[name];
if (component) {
// Import external data
this._importComponentExternalData(name, external, options);
}
else {
// Get component class type
let componentClassType = onGetObjectClassType(componentData);
if (!componentClassType) {
if (onError) {
onError(`Load components failed, due to '${componentData.type}' is unkonwn`);
}
return;
}
// Register component by class type
let params = Object.assign({}, external);
Object.assign(params, options);
delete params.args;
params = Object.assign(params, options['args']);
this.addComponent(componentClassType, name, params);
// Import external data
this._importComponentExternalData(name, external, options);
}
});
}
onExportComponents(options = {}) {
let onGetComponentType = options['onGetComponentType'];
let components = [];
// Export all components
this.components.forEach((component, name) => {
if (!component.onExport) {
return;
}
// Export data from component
let data = component.onExport(options);
if (!data) {
return;
}
// Get the component type
let type;
if (onGetComponentType) {
type = onGetComponentType(component.constructor.name);
}
else {
type = component.constructor.name;
}
// Update components
components.push({
name,
type,
params: data
});
});
return components;
}
onImportExternalComponents(externalComponents) {
return Promise.resolve();
}
onExportExternalComponents() {
}
/**
* Get the tickable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get tickableComponents() {
let _private = this[__.private];
return _private.tickableComponents;
}
/**
* Get the renderable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get renderableComponents() {
let _private = this[__.private];
return _private.renderableComponents;
}
/**
* Get the resizable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get resizableComponents() {
let _private = this[__.private];
return _private.resizableComponents;
}
/**
* Get the refreshable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get refreshableComponents() {
let _private = this[__.private];
return _private.refreshableComponents;
}
/**
* Get the loadable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get loadableComponents() {
let _private = this[__.private];
return _private.loadableComponents;
}
/**
* Get the unloadable components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get unloadableComponents() {
let _private = this[__.private];
return _private.unloadableComponents;
}
/**
* Get the visible components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get visibleComponents() {
let _private = this[__.private];
return _private.visibleComponents;
}
/**
* Get the parent change components.
* @type {Array<THING.BaseComponent>}
* @private
*/
get parentChangeComponents() {
let _private = this[__.private];
return _private.parentChangeComponents;
}
/**
* Get all components.
* @type {Map<String, THING.BaseComponent>}
* @readonly
* @public
* @example
* let obj = new THING.BaseObject();;
* obj.addComponent(new THING.BaseComponent(), 'component1');
* obj.addComponent(new THING.BaseComponent(), 'component2');
* // @expect(obj.components.size == 2)
*/
get components() {
return this[__.private].components;
}
get isComponentGroup() {
return true;
}
}
export { BaseComponentGroup }