Spaces:
Running
Running
| /* | |
| * 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; | |