|  | /* | 
|  | * 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 env from 'zrender/src/core/env'; | 
|  | import { | 
|  | enableClassExtend, | 
|  | ExtendableConstructor, | 
|  | enableClassCheck, | 
|  | CheckableConstructor | 
|  | } from '../util/clazz'; | 
|  |  | 
|  | import {AreaStyleMixin} from './mixin/areaStyle'; | 
|  | import TextStyleMixin from './mixin/textStyle'; | 
|  | import {LineStyleMixin} from './mixin/lineStyle'; | 
|  | import {ItemStyleMixin} from './mixin/itemStyle'; | 
|  | import GlobalModel from './Global'; | 
|  | import { AnimationOptionMixin, ModelOption } from '../util/types'; | 
|  | import { Dictionary } from 'zrender/src/core/types'; | 
|  | import { mixin, clone, merge } from 'zrender/src/core/util'; | 
|  |  | 
|  | // Since model.option can be not only `Dictionary` but also primary types, | 
|  | // we do this conditional type to avoid getting type 'never'; | 
|  | // type Key<Opt> = Opt extends Dictionary<any> | 
|  | //     ? keyof Opt : string; | 
|  | // type Value<Opt, R> = Opt extends Dictionary<any> | 
|  | //     ? (R extends keyof Opt ? Opt[R] : ModelOption) | 
|  | //     : ModelOption; | 
|  |  | 
|  | // eslint-disable-next-line @typescript-eslint/no-unused-vars | 
|  | interface Model<Opt = ModelOption> | 
|  | extends LineStyleMixin, ItemStyleMixin, TextStyleMixin, AreaStyleMixin {} | 
|  | class Model<Opt = ModelOption> {    // TODO: TYPE use unknown instead of any? | 
|  |  | 
|  | // [Caution]: Because this class or desecendants can be used as `XXX.extend(subProto)`, | 
|  | // the class members must not be initialized in constructor or declaration place. | 
|  | // Otherwise there is bad case: | 
|  | //   class A {xxx = 1;} | 
|  | //   enableClassExtend(A); | 
|  | //   class B extends A {} | 
|  | //   var C = B.extend({xxx: 5}); | 
|  | //   var c = new C(); | 
|  | //   console.log(c.xxx); // expect 5 but always 1. | 
|  |  | 
|  | parentModel: Model; | 
|  |  | 
|  | ecModel: GlobalModel; | 
|  |  | 
|  | option: Opt;    // TODO Opt should only be object. | 
|  |  | 
|  | constructor(option?: Opt, parentModel?: Model, ecModel?: GlobalModel) { | 
|  | this.parentModel = parentModel; | 
|  | this.ecModel = ecModel; | 
|  | this.option = option; | 
|  |  | 
|  | // Simple optimization | 
|  | // if (this.init) { | 
|  | //     if (arguments.length <= 4) { | 
|  | //         this.init(option, parentModel, ecModel, extraOpt); | 
|  | //     } | 
|  | //     else { | 
|  | //         this.init.apply(this, arguments); | 
|  | //     } | 
|  | // } | 
|  | } | 
|  |  | 
|  | init(option: Opt, parentModel?: Model, ecModel?: GlobalModel, ...rest: any): void {} | 
|  |  | 
|  | /** | 
|  | * Merge the input option to me. | 
|  | */ | 
|  | mergeOption(option: Opt, ecModel?: GlobalModel): void { | 
|  | merge(this.option, option, true); | 
|  | } | 
|  |  | 
|  | // FIXME:TS consider there is parentModel, | 
|  | // return type have to be ModelOption or can be Option<R>? | 
|  | // (Is there any chance that parentModel value type is different?) | 
|  | get<R extends keyof Opt>( | 
|  | path: R, ignoreParent?: boolean | 
|  | ): Opt[R]; | 
|  | get<R extends keyof Opt>( | 
|  | path: readonly [R], ignoreParent?: boolean | 
|  | ): Opt[R]; | 
|  | get<R extends keyof Opt, S extends keyof Opt[R]>( | 
|  | path: readonly [R, S], ignoreParent?: boolean | 
|  | ): Opt[R][S]; | 
|  | get<R extends keyof Opt, S extends keyof Opt[R], T extends keyof Opt[R][S]>( | 
|  | path: readonly [R, S, T], ignoreParent?: boolean | 
|  | ): Opt[R][S][T]; | 
|  | // `path` can be 'a.b.c', so the return value type have to be `ModelOption` | 
|  | // TODO: TYPE strict key check? | 
|  | // get(path: string | string[], ignoreParent?: boolean): ModelOption; | 
|  | get(path: string | readonly string[], ignoreParent?: boolean): ModelOption { | 
|  | if (path == null) { | 
|  | return this.option; | 
|  | } | 
|  |  | 
|  | return this._doGet( | 
|  | this.parsePath(path), | 
|  | !ignoreParent && this.parentModel | 
|  | ); | 
|  | } | 
|  |  | 
|  | getShallow<R extends keyof Opt>( | 
|  | key: R, ignoreParent?: boolean | 
|  | ): Opt[R] { | 
|  | const option = this.option; | 
|  |  | 
|  | let val = option == null ? option : option[key]; | 
|  | if (val == null && !ignoreParent) { | 
|  | const parentModel = this.parentModel; | 
|  | if (parentModel) { | 
|  | // FIXME:TS do not know how to make it works | 
|  | val = parentModel.getShallow(key); | 
|  | } | 
|  | } | 
|  | return val as Opt[R]; | 
|  | } | 
|  |  | 
|  | // TODO At most 3 depth? | 
|  | getModel<R extends keyof Opt>( | 
|  | path: R, parentModel?: Model | 
|  | ): Model<Opt[R]>; | 
|  | getModel<R extends keyof Opt>( | 
|  | path: readonly [R], parentModel?: Model | 
|  | ): Model<Opt[R]>; | 
|  | getModel<R extends keyof Opt, S extends keyof Opt[R]>( | 
|  | path: readonly [R, S], parentModel?: Model | 
|  | ): Model<Opt[R][S]>; | 
|  | getModel<Ra extends keyof Opt, Rb extends keyof Opt, S extends keyof Opt[Rb]>( | 
|  | path: readonly [Ra] | readonly [Rb, S], parentModel?: Model | 
|  | ): Model<Opt[Ra]> | Model<Opt[Rb][S]>; | 
|  | getModel<R extends keyof Opt, S extends keyof Opt[R], T extends keyof Opt[R][S]>( | 
|  | path: readonly [R, S, T], parentModel?: Model | 
|  | ): Model<Opt[R][S][T]>; | 
|  | // `path` can be 'a.b.c', so the return value type have to be `Model<ModelOption>` | 
|  | // getModel(path: string | string[], parentModel?: Model): Model; | 
|  | // TODO 'a.b.c' is deprecated | 
|  | getModel(path: string | readonly string[], parentModel?: Model): Model<any> { | 
|  | const hasPath = path != null; | 
|  | const pathFinal = hasPath ? this.parsePath(path) : null; | 
|  | const obj = hasPath | 
|  | ? this._doGet(pathFinal) | 
|  | : this.option; | 
|  |  | 
|  | parentModel = parentModel || ( | 
|  | this.parentModel | 
|  | && this.parentModel.getModel(this.resolveParentPath(pathFinal) as [string]) | 
|  | ); | 
|  |  | 
|  | return new Model(obj, parentModel, this.ecModel); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * If model has option | 
|  | */ | 
|  | isEmpty(): boolean { | 
|  | return this.option == null; | 
|  | } | 
|  |  | 
|  | restoreData(): void {} | 
|  |  | 
|  | // Pending | 
|  | clone(): Model<Opt> { | 
|  | const Ctor = this.constructor; | 
|  | return new (Ctor as any)(clone(this.option)); | 
|  | } | 
|  |  | 
|  | // setReadOnly(properties): void { | 
|  | // clazzUtil.setReadOnly(this, properties); | 
|  | // } | 
|  |  | 
|  | // If path is null/undefined, return null/undefined. | 
|  | parsePath(path: string | readonly string[]): readonly string[] { | 
|  | if (typeof path === 'string') { | 
|  | return path.split('.'); | 
|  | } | 
|  | return path; | 
|  | } | 
|  |  | 
|  | // Resolve path for parent. Perhaps useful when parent use a different property. | 
|  | // Default to be a identity resolver. | 
|  | // Can be modified to a different resolver. | 
|  | resolveParentPath(path: readonly string[]): string[] { | 
|  | return path as string[]; | 
|  | } | 
|  |  | 
|  | // FIXME:TS check whether put this method here | 
|  | isAnimationEnabled(): boolean { | 
|  | if (!env.node && this.option) { | 
|  | if ((this.option as AnimationOptionMixin).animation != null) { | 
|  | return !!(this.option as AnimationOptionMixin).animation; | 
|  | } | 
|  | else if (this.parentModel) { | 
|  | return this.parentModel.isAnimationEnabled(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private _doGet(pathArr: readonly string[], parentModel?: Model<Dictionary<any>>) { | 
|  | let obj = this.option; | 
|  | if (!pathArr) { | 
|  | return obj; | 
|  | } | 
|  |  | 
|  | for (let i = 0; i < pathArr.length; i++) { | 
|  | // Ignore empty | 
|  | if (!pathArr[i]) { | 
|  | continue; | 
|  | } | 
|  | // obj could be number/string/... (like 0) | 
|  | obj = (obj && typeof obj === 'object') | 
|  | ? (obj as ModelOption)[pathArr[i] as keyof ModelOption] : null; | 
|  | if (obj == null) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (obj == null && parentModel) { | 
|  | obj = parentModel._doGet( | 
|  | this.resolveParentPath(pathArr) as [string], | 
|  | parentModel.parentModel | 
|  | ) as any; | 
|  | } | 
|  |  | 
|  | return obj; | 
|  | } | 
|  | }; | 
|  |  | 
|  | type ModelConstructor = typeof Model | 
|  | & ExtendableConstructor | 
|  | & CheckableConstructor; | 
|  |  | 
|  | // Enable Model.extend. | 
|  | enableClassExtend(Model as ModelConstructor); | 
|  | enableClassCheck(Model as ModelConstructor); | 
|  |  | 
|  |  | 
|  | mixin(Model, LineStyleMixin); | 
|  | mixin(Model, ItemStyleMixin); | 
|  | mixin(Model, AreaStyleMixin); | 
|  | mixin(Model, TextStyleMixin); | 
|  |  | 
|  | export default Model; |