| /* |
| * 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]: Becuase 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 'xxx.yyy.zzz', 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 'xxx.yyy.zzz', so the return value type have to be `Model<ModelOption>` |
| // getModel(path: string | string[], parentModel?: Model): Model; |
| // TODO 'xxx.yyy.zzz' 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; |