|  | /* | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements.  See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership.  The ASF licenses this file | 
|  | * to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance | 
|  | * with the License.  You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, | 
|  | * software distributed under the License is distributed on an | 
|  | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | * KIND, either express or implied.  See the License for the | 
|  | * specific language governing permissions and limitations | 
|  | * under the License. | 
|  | */ | 
|  |  | 
|  | import * as zrUtil from 'zrender/src/core/util'; | 
|  | import { Dictionary } from 'zrender/src/core/types'; | 
|  | import { ComponentFullType, ComponentTypeInfo, ComponentMainType, ComponentSubType } from './types'; | 
|  |  | 
|  | const TYPE_DELIMITER = '.'; | 
|  | const IS_CONTAINER = '___EC__COMPONENT__CONTAINER___' as const; | 
|  | const IS_EXTENDED_CLASS = '___EC__EXTENDED_CLASS___' as const; | 
|  |  | 
|  | /** | 
|  | * Notice, parseClassType('') should returns {main: '', sub: ''} | 
|  | * @public | 
|  | */ | 
|  | export function parseClassType(componentType: ComponentFullType): ComponentTypeInfo { | 
|  | const ret = {main: '', sub: ''}; | 
|  | if (componentType) { | 
|  | const typeArr = componentType.split(TYPE_DELIMITER); | 
|  | ret.main = typeArr[0] || ''; | 
|  | ret.sub = typeArr[1] || ''; | 
|  | } | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @public | 
|  | */ | 
|  | function checkClassType(componentType: ComponentFullType): void { | 
|  | zrUtil.assert( | 
|  | /^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(componentType), | 
|  | 'componentType "' + componentType + '" illegal' | 
|  | ); | 
|  | } | 
|  |  | 
|  | export function isExtendedClass(clz: any): boolean { | 
|  | return !!(clz && clz[IS_EXTENDED_CLASS]); | 
|  | } | 
|  |  | 
|  |  | 
|  | export interface ExtendableConstructor { | 
|  | new (...args: any): any; | 
|  | $constructor?: new (...args: any) => any; | 
|  | extend: (proto: {[name: string]: any}) => ExtendableConstructor; | 
|  | superCall: (context: any, methodName: string, ...args: any) => any; | 
|  | superApply: (context: any, methodName: string, args: []) => any; | 
|  | superClass?: ExtendableConstructor; | 
|  | [IS_EXTENDED_CLASS]?: boolean; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implements `ExtendableConstructor` for `rootClz`. | 
|  | * | 
|  | * @usage | 
|  | * ```ts | 
|  | * class Xxx {} | 
|  | * type XxxConstructor = typeof Xxx & ExtendableConstructor | 
|  | * enableClassExtend(Xxx as XxxConstructor); | 
|  | * ``` | 
|  | */ | 
|  | export function enableClassExtend(rootClz: ExtendableConstructor, mandatoryMethods?: string[]): void { | 
|  |  | 
|  | rootClz.$constructor = rootClz; // FIXME: not necessary? | 
|  |  | 
|  | rootClz.extend = function (proto: Dictionary<any>) { | 
|  | if (__DEV__) { | 
|  | zrUtil.each(mandatoryMethods, function (method) { | 
|  | if (!proto[method]) { | 
|  | console.warn( | 
|  | 'Method `' + method + '` should be implemented' | 
|  | + (proto.type ? ' in ' + proto.type : '') + '.' | 
|  | ); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | const superClass = this; | 
|  | let ExtendedClass: any; | 
|  |  | 
|  | if (isESClass(superClass)) { | 
|  | ExtendedClass = class extends superClass { | 
|  | constructor() { | 
|  | super(...arguments as any); | 
|  | } | 
|  | }; | 
|  | } | 
|  | else { | 
|  | // For backward compat, we both support ts class inheritance and this | 
|  | // "extend" approach. | 
|  | // The constructor should keep the same behavior as ts class inheritance: | 
|  | // If this constructor/$constructor is not declared, auto invoke the super | 
|  | // constructor. | 
|  | // If this constructor/$constructor is declared, it is responsible for | 
|  | // calling the super constructor. | 
|  | ExtendedClass = function (this: any) { | 
|  | (proto.$constructor || superClass).apply(this, arguments); | 
|  | }; | 
|  |  | 
|  | zrUtil.inherits(ExtendedClass, this); | 
|  | } | 
|  |  | 
|  | zrUtil.extend(ExtendedClass.prototype, proto); | 
|  | ExtendedClass[IS_EXTENDED_CLASS] = true; | 
|  |  | 
|  | ExtendedClass.extend = this.extend; | 
|  | ExtendedClass.superCall = superCall; | 
|  | ExtendedClass.superApply = superApply; | 
|  | ExtendedClass.superClass = superClass; | 
|  |  | 
|  | return ExtendedClass as unknown as ExtendableConstructor; | 
|  | }; | 
|  | } | 
|  |  | 
|  | function isESClass(fn: unknown): boolean { | 
|  | return zrUtil.isFunction(fn) | 
|  | && /^class\s/.test(Function.prototype.toString.call(fn)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A work around to both support ts extend and this extend mechanism. | 
|  | * on sub-class. | 
|  | * @usage | 
|  | * ```ts | 
|  | * class Component { ... } | 
|  | * classUtil.enableClassExtend(Component); | 
|  | * classUtil.enableClassManagement(Component, {registerWhenExtend: true}); | 
|  | * | 
|  | * class Series extends Component { ... } | 
|  | * // Without calling `markExtend`, `registerWhenExtend` will not work. | 
|  | * Component.markExtend(Series); | 
|  | * ``` | 
|  | */ | 
|  | export function mountExtend(SubClz: any, SupperClz: any): void { | 
|  | SubClz.extend = SupperClz.extend; | 
|  | } | 
|  |  | 
|  |  | 
|  | export interface CheckableConstructor { | 
|  | new (...args: any): any; | 
|  | isInstance: (ins: any) => boolean; | 
|  | } | 
|  |  | 
|  | // A random offset. | 
|  | let classBase = Math.round(Math.random() * 10); | 
|  |  | 
|  | /** | 
|  | * Implements `CheckableConstructor` for `target`. | 
|  | * Can not use instanceof, consider different scope by | 
|  | * cross domain or es module import in ec extensions. | 
|  | * Mount a method "isInstance()" to Clz. | 
|  | * | 
|  | * @usage | 
|  | * ```ts | 
|  | * class Xxx {} | 
|  | * type XxxConstructor = typeof Xxx & CheckableConstructor; | 
|  | * enableClassCheck(Xxx as XxxConstructor) | 
|  | * ``` | 
|  | */ | 
|  | export function enableClassCheck(target: CheckableConstructor): void { | 
|  | const classAttr = ['__\0is_clz', classBase++].join('_'); | 
|  | target.prototype[classAttr] = true; | 
|  |  | 
|  | if (__DEV__) { | 
|  | zrUtil.assert(!target.isInstance, 'The method "is" can not be defined.'); | 
|  | } | 
|  |  | 
|  | target.isInstance = function (obj) { | 
|  | return !!(obj && obj[classAttr]); | 
|  | }; | 
|  | } | 
|  |  | 
|  | // superCall should have class info, which can not be fetched from 'this'. | 
|  | // Consider this case: | 
|  | // class A has method f, | 
|  | // class B inherits class A, overrides method f, f call superApply('f'), | 
|  | // class C inherits class B, does not override method f, | 
|  | // then when method of class C is called, dead loop occurred. | 
|  | function superCall(this: any, context: any, methodName: string, ...args: any): any { | 
|  | return this.superClass.prototype[methodName].apply(context, args); | 
|  | } | 
|  |  | 
|  | function superApply(this: any, context: any, methodName: string, args: any): any { | 
|  | return this.superClass.prototype[methodName].apply(context, args); | 
|  | } | 
|  |  | 
|  | export type Constructor = new (...args: any) => any; | 
|  | type SubclassContainer = {[subType: string]: Constructor} & {[IS_CONTAINER]?: true}; | 
|  |  | 
|  | export interface ClassManager { | 
|  | registerClass: (clz: Constructor) => Constructor; | 
|  | getClass: ( | 
|  | componentMainType: ComponentMainType, subType?: ComponentSubType, throwWhenNotFound?: boolean | 
|  | ) => Constructor; | 
|  | getClassesByMainType: (componentType: ComponentMainType) => Constructor[]; | 
|  | hasClass: (componentType: ComponentFullType) => boolean; | 
|  | getAllClassMainTypes: () => ComponentMainType[]; | 
|  | hasSubTypes: (componentType: ComponentFullType) => boolean; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implements `ClassManager` for `target` | 
|  | * | 
|  | * @usage | 
|  | * ```ts | 
|  | * class Xxx {} | 
|  | * type XxxConstructor = typeof Xxx & ClassManager | 
|  | * enableClassManagement(Xxx as XxxConstructor); | 
|  | * ``` | 
|  | */ | 
|  | export function enableClassManagement( | 
|  | target: ClassManager | 
|  | ): void { | 
|  |  | 
|  | /** | 
|  | * Component model classes | 
|  | * key: componentType, | 
|  | * value: | 
|  | *     componentClass, when componentType is 'a' | 
|  | *     or Object.<subKey, componentClass>, when componentType is 'a.b' | 
|  | */ | 
|  | const storage: { | 
|  | [componentMainType: string]: (Constructor | SubclassContainer) | 
|  | } = {}; | 
|  |  | 
|  | target.registerClass = function ( | 
|  | clz: Constructor | 
|  | ): Constructor { | 
|  |  | 
|  | // `type` should not be a "instance member". | 
|  | // If using TS class, should better declared as `static type = 'series.pie'`. | 
|  | // otherwise users have to mount `type` on prototype manually. | 
|  | // For backward compat and enable instance visit type via `this.type`, | 
|  | // we still support fetch `type` from prototype. | 
|  | const componentFullType = (clz as any).type || clz.prototype.type; | 
|  |  | 
|  | if (componentFullType) { | 
|  | checkClassType(componentFullType); | 
|  |  | 
|  | // If only static type declared, we assign it to prototype mandatorily. | 
|  | clz.prototype.type = componentFullType; | 
|  |  | 
|  | const componentTypeInfo = parseClassType(componentFullType); | 
|  |  | 
|  | if (!componentTypeInfo.sub) { | 
|  | if (__DEV__) { | 
|  | if (storage[componentTypeInfo.main]) { | 
|  | console.warn(componentTypeInfo.main + ' exists.'); | 
|  | } | 
|  | } | 
|  | storage[componentTypeInfo.main] = clz; | 
|  | } | 
|  | else if (componentTypeInfo.sub !== IS_CONTAINER) { | 
|  | const container = makeContainer(componentTypeInfo); | 
|  | container[componentTypeInfo.sub] = clz; | 
|  | } | 
|  | } | 
|  | return clz; | 
|  | }; | 
|  |  | 
|  | target.getClass = function ( | 
|  | mainType: ComponentMainType, | 
|  | subType?: ComponentSubType, | 
|  | throwWhenNotFound?: boolean | 
|  | ): Constructor { | 
|  | let clz = storage[mainType]; | 
|  |  | 
|  | if (clz && (clz as SubclassContainer)[IS_CONTAINER]) { | 
|  | clz = subType ? (clz as SubclassContainer)[subType] : null; | 
|  | } | 
|  |  | 
|  | if (throwWhenNotFound && !clz) { | 
|  | throw new Error( | 
|  | !subType | 
|  | ? mainType + '.' + 'type should be specified.' | 
|  | : 'Component ' + mainType + '.' + (subType || '') + ' is used but not imported.' | 
|  | ); | 
|  | } | 
|  |  | 
|  | return clz as Constructor; | 
|  | }; | 
|  |  | 
|  | target.getClassesByMainType = function (componentType: ComponentFullType): Constructor[] { | 
|  | const componentTypeInfo = parseClassType(componentType); | 
|  |  | 
|  | const result: Constructor[] = []; | 
|  | const obj = storage[componentTypeInfo.main]; | 
|  |  | 
|  | if (obj && (obj as SubclassContainer)[IS_CONTAINER]) { | 
|  | zrUtil.each(obj as SubclassContainer, function (o, type) { | 
|  | type !== IS_CONTAINER && result.push(o as Constructor); | 
|  | }); | 
|  | } | 
|  | else { | 
|  | result.push(obj as Constructor); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | }; | 
|  |  | 
|  | target.hasClass = function (componentType: ComponentFullType): boolean { | 
|  | // Just consider componentType.main. | 
|  | const componentTypeInfo = parseClassType(componentType); | 
|  | return !!storage[componentTypeInfo.main]; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @return Like ['aa', 'bb'], but can not be ['aa.xx'] | 
|  | */ | 
|  | target.getAllClassMainTypes = function (): ComponentMainType[] { | 
|  | const types: string[] = []; | 
|  | zrUtil.each(storage, function (obj, type) { | 
|  | types.push(type); | 
|  | }); | 
|  | return types; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * If a main type is container and has sub types | 
|  | */ | 
|  | target.hasSubTypes = function (componentType: ComponentFullType): boolean { | 
|  | const componentTypeInfo = parseClassType(componentType); | 
|  | const obj = storage[componentTypeInfo.main]; | 
|  | return obj && (obj as SubclassContainer)[IS_CONTAINER]; | 
|  | }; | 
|  |  | 
|  | function makeContainer(componentTypeInfo: ComponentTypeInfo): SubclassContainer { | 
|  | let container = storage[componentTypeInfo.main]; | 
|  | if (!container || !(container as SubclassContainer)[IS_CONTAINER]) { | 
|  | container = storage[componentTypeInfo.main] = {}; | 
|  | container[IS_CONTAINER] = true; | 
|  | } | 
|  | return container as SubclassContainer; | 
|  | } | 
|  | } | 
|  |  | 
|  | // /** | 
|  | //  * @param {string|Array.<string>} properties | 
|  | //  */ | 
|  | // export function setReadOnly(obj, properties) { | 
|  | // FIXME It seems broken in IE8 simulation of IE11 | 
|  | // if (!zrUtil.isArray(properties)) { | 
|  | //     properties = properties != null ? [properties] : []; | 
|  | // } | 
|  | // zrUtil.each(properties, function (prop) { | 
|  | //     let value = obj[prop]; | 
|  |  | 
|  | //     Object.defineProperty | 
|  | //         && Object.defineProperty(obj, prop, { | 
|  | //             value: value, writable: false | 
|  | //         }); | 
|  | //     zrUtil.isArray(obj[prop]) | 
|  | //         && Object.freeze | 
|  | //         && Object.freeze(obj[prop]); | 
|  | // }); | 
|  | // } |