From bf62999782ab88c9565f5b5327d3a54a8881e72e Mon Sep 17 00:00:00 2001 From: Ilya Erokhin Date: Wed, 27 Aug 2025 15:38:13 +0300 Subject: [PATCH] Move ArkStateProperties.ets to decorator.ts Signed-off-by: Ilya Erokhin --- arkoala-arkts/arkui/sdk/src/index.ets | 2 +- .../decorator.ts} | 4 +- .../arkui/src/ArkStateProperties.ets | 320 ------------------ arkoala-arkts/arkui/src/index.ets | 1 - .../arkui/src/stateManagement/decorator.ts | 306 ++++++++++++++++- 5 files changed, 308 insertions(+), 325 deletions(-) rename arkoala-arkts/arkui/sdk/src/{ArkStateProperties.ets => stateManagement/decorator.ts} (98%) delete mode 100644 arkoala-arkts/arkui/src/ArkStateProperties.ets diff --git a/arkoala-arkts/arkui/sdk/src/index.ets b/arkoala-arkts/arkui/sdk/src/index.ets index c2c4f415e..502da4238 100644 --- a/arkoala-arkts/arkui/sdk/src/index.ets +++ b/arkoala-arkts/arkui/sdk/src/index.ets @@ -20,7 +20,6 @@ export * from "./ArkAnimation" export * from "./ArkPageTransition" export * from "./ArkPageTransitionData" export * from "./ArkState" -export * from "./ArkStateProperties" export * from "./ArkStateStyle.ets" export * from "./AttributeUpdater" export * from "./LazyForEach" @@ -38,3 +37,4 @@ export * from "./SymbolGlyphModifier" export * from "./TextModifier" export * from "./UserView" export * from "./component/forEach" +export * from "./stateManagement/decorator" diff --git a/arkoala-arkts/arkui/sdk/src/ArkStateProperties.ets b/arkoala-arkts/arkui/sdk/src/stateManagement/decorator.ts similarity index 98% rename from arkoala-arkts/arkui/sdk/src/ArkStateProperties.ets rename to arkoala-arkts/arkui/sdk/src/stateManagement/decorator.ts index f28e2c0f1..0c629e2be 100644 --- a/arkoala-arkts/arkui/sdk/src/ArkStateProperties.ets +++ b/arkoala-arkts/arkui/sdk/src/stateManagement/decorator.ts @@ -13,8 +13,8 @@ * limitations under the License. */ -import { SubscribedAbstractProperty } from "./ArkState" -import { LocalStorage } from "./Storage" +import { SubscribedAbstractProperty } from "../ArkState" +import { LocalStorage } from "../Storage" export declare class PlainStructProperty implements SubscribedAbstractProperty { constructor(name: string, value?: Value) diff --git a/arkoala-arkts/arkui/src/ArkStateProperties.ets b/arkoala-arkts/arkui/src/ArkStateProperties.ets deleted file mode 100644 index ddd76c6af..000000000 --- a/arkoala-arkts/arkui/src/ArkStateProperties.ets +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (c) 2022-2025 Huawei Device Co., Ltd. - * Licensed 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 { int32, observableProxy, propDeepCopy } from "@koalaui/common" -import { mutableState, scheduleCallback, MutableState, GlobalStateManager, globalMutableState, ObservableClass, ClassMetadata } from "@koalaui/runtime" -import { SubscribedAbstractProperty } from "./ArkState" -import { AppStorage, LocalStorage } from "./Storage" - -class StatableHolder { - private directValue: Value | undefined = undefined - private state: MutableState | undefined = undefined - - constructor(value: Value | undefined) { - if (value != undefined) { - this.setValue(value) - } - } - - get value(): Value { - return this.state ? this.state!.value! : this.directValue! - } - - set value(value: Value) { - this.setValue(value) - } - - dispose() { - this.state?.dispose() - } - - private setValue(value: Value) { - if (this.isStatable(value)) { - this.setStateValue(observableProxy(value)) - } else { - if (this.state) { - throw new Error(`The property is already bound to a mutable state value`) - } - this.directValue = value - } - } - - private setStateValue(value: Value) { - if (!this.state) { - this.state = globalMutableState(value) - } - this.state!.value = value - } - - private isStatable(value: Value | undefined): boolean { - if (value instanceof ObservableClass) { - return value.getClassMetadata()?.isObservedV2(value) ?? false - } - return false - } -} - -export class PlainStructProperty implements SubscribedAbstractProperty { - private name: string - private readonly holder: StatableHolder - - constructor(name: string, value?: Value) { - this.name = name - this.holder = new StatableHolder(value) - } - - init(value: Value | undefined): void { - if (value != undefined) - this.holder.value = value - } - - info(): string { - return this.name - } - - get(): Value { - return this.holder.value! - } - - set(value: Value): void { - this.holder.value = value - } - - subscribe(listener: () => void): void { - } - - unsubscribe(listener: () => void): void { - } - - aboutToBeDeleted(): void { - this.holder.dispose() - } -} - -export class BuilderParamDecoratorProperty extends PlainStructProperty { - constructor(name: string, value?: Value) { - super(name, value) - } -} - -export class LinkDecoratorProperty implements SubscribedAbstractProperty { - private readonly name: string - private readonly watch: (() => void) | undefined - private property: SubscribedAbstractProperty | undefined = undefined - - constructor(name: string, watch?: () => void) { - this.name = name - this.watch = watch - } - - linkTo(maybeProperty: SubscribedAbstractProperty | Value | undefined): void { - if (!maybeProperty) throw new Error(`${this.name} must be linked with another property`) - const property = maybeProperty! // Improve: this is to workaround Any considered non-nulish - if (!(property instanceof SubscribedAbstractProperty)) throw new Error('Property must be passed, got') - if (this.property) throw new Error(`${this.name} is already linked with some property`) - this.property = property - if (this.watch) property.subscribe(this.watch!) - } - - info(): string { - return this.name - } - - get(): Value { - return this.property!.get() - } - - set(value: Value): void { - this.property!.set(value) - } - - subscribe(listener: () => void): void { - this.property!.unsubscribe(listener) - } - - unsubscribe(listener: () => void): void { - this.property!.unsubscribe(listener) - } - - aboutToBeDeleted(): void { - if (this.watch) this.property!.unsubscribe(this.watch!) - } -} - -export class StateDecoratorProperty implements SubscribedAbstractProperty { - private name: string - private state: MutableState | undefined = undefined - private listeners: Set<() => void> | undefined = undefined - - constructor(name: string, listener?: () => void) { - this.name = name - if (listener) this.subscribe(listener) - } - init(value?: Value, initial?: Value): void { - this.state = mutableState(observableProxy(value ?? (initial as Value))) - } - info(): string { - return this.name - } - get(): Value { - return this.state!.value - } - set(value: Value): void { - this.state!.value = observableProxy(value) - this.listeners?.forEach(notify) - } - subscribe(listener: () => void): void { - if (!this.listeners) this.listeners = new Set<() => void>() - this.listeners?.add(listener) - } - unsubscribe(listener: () => void): void { - this.listeners?.delete(listener) - } - aboutToBeDeleted(): void { - this.listeners?.clear() - } -} - -function notify(listener: () => void) { - listener() -} - -export class PropDecoratorProperty extends StateDecoratorProperty { - /* - _modified and _value needed for changes to be observable instantly, on the same recomputation value is being changed - */ - private _modified = false - private _value?: Value - - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(value?: Value, initial?: Value): void { - super.init(value ? this.deepCopyOnUpdate(value) : undefined, initial) - } - get(): Value { - let value = super.get() // subscribe - if (this._modified) value = this._value as Value - return value - } - update(value?: Value): void { - this._modified = false - this._value = undefined - const scope = GlobalStateManager.instance.scope(0, 1) - const parameter = scope.param(0, value) - if (scope.unchanged) { - scope.cached - return - } - value = parameter.value // subscribe to update - if (value != undefined) { - const copy = this.deepCopyOnUpdate(value) - this._modified = true - this._value = copy - scheduleCallback(() => { this.set(copy) }) - } - scope.recache() - } - protected deepCopyOnUpdate(value: Value): Value { - return observableProxy(propDeepCopy(value)) - } -} - -export class ObjectLinkDecoratorProperty extends PropDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - protected deepCopyOnUpdate(value: Value): Value { - return value - } -} - -export class ProvideDecoratorProperty extends StateDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - provide(provideKey?: string): void { - GlobalStateManager.instance.namedState>(provideKey ?? this.info(), () => this) - } - checkOverrides(provideKey?: string): void { - const actualProvideKey = provideKey ?? this.info() - const state = GlobalStateManager.instance.stateBy(actualProvideKey, false) - if (state) { - throw new Error(`Variable "${actualProvideKey}" was already defined on the current page. ` + - `Use @Provide({allowOverride: "${actualProvideKey}"}) for explicit override or choose another variable name`) - } - } -} - -export class ConsumeDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(provideKey?: string): void { - this.linkTo(GlobalStateManager.instance.valueBy>(provideKey ?? this.info())) - } -} - -export class ConsumerDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(defaultValue: Value, provideKey?: string): void { - const provided = GlobalStateManager.instance.stateBy(provideKey ?? this.info()) - if (provided != undefined) { - this.linkTo(provided.value) - } else { - const state = new StateDecoratorProperty(this.info()) - state.init(undefined, defaultValue) - this.linkTo(state) - } - } -} - -export class StorageLinkDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(value: Value, storageKey?: string): void { - this.linkTo(AppStorage.setAndLink(storageKey ?? this.info(), value)) - } -} - -export class LocalStorageLinkDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(value: Value, storage: LocalStorage, storageKey?: string): void { - this.linkTo(storage.setAndLink(storageKey ?? this.info(), value)) - } -} - -export class StoragePropDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(value: Value, storageKey?: string): void { - this.linkTo(AppStorage.setAndProp(storageKey ?? this.info(), value)) - } -} - -export class LocalStoragePropDecoratorProperty extends LinkDecoratorProperty { - constructor(name: string, listener?: () => void) { - super(name, listener) - } - init(value: Value, storage: LocalStorage, storageKey?: string): void { - this.linkTo(storage.setAndProp(storageKey ?? this.info(), value)) - } -} diff --git a/arkoala-arkts/arkui/src/index.ets b/arkoala-arkts/arkui/src/index.ets index dcc7ad97a..d12e79cf6 100644 --- a/arkoala-arkts/arkui/src/index.ets +++ b/arkoala-arkts/arkui/src/index.ets @@ -17,7 +17,6 @@ export * from "./stateAnnotations" export * from "./componentAnnotations" export * from "./ArkState" -export * from "./ArkStateProperties" export * from "./NativeLog" export * from "./UserView" export * from "./LazyForEach" diff --git a/arkoala-arkts/arkui/src/stateManagement/decorator.ts b/arkoala-arkts/arkui/src/stateManagement/decorator.ts index 8f86f596f..44242e1c9 100644 --- a/arkoala-arkts/arkui/src/stateManagement/decorator.ts +++ b/arkoala-arkts/arkui/src/stateManagement/decorator.ts @@ -14,11 +14,14 @@ */ import { ObserveSingleton } from './base/observeSingleton'; -import { int32 } from '@koalaui/common'; +import { int32, observableProxy, propDeepCopy } from "@koalaui/common" import { __StateMgmtFactoryImpl } from './base/stateMgmtFactory'; import { ExtendableComponent } from '../component/extendableComponent'; import { IBindingSource, ITrackedDecoratorRef } from './base/mutableStateMeta'; import { IComputedDecoratorRef } from './decoratorImpl/decoratorComputed'; +import { mutableState, scheduleCallback, MutableState, GlobalStateManager, globalMutableState, ObservableClass, ClassMetadata } from "@koalaui/runtime" +import { SubscribedAbstractProperty } from "../ArkState" +import { AppStorage, LocalStorage } from "../Storage" export interface IDecoratedVariable { readonly varName: string; @@ -256,3 +259,304 @@ export interface IMonitorValue { export type MonitorValueCallback = () => Any; export type MonitorCallback = (m: IMonitor) => void; export type ComputeCallback = () => T; + +class StatableHolder { + private directValue: Value | undefined = undefined + private state: MutableState | undefined = undefined + + constructor(value: Value | undefined) { + if (value != undefined) { + this.setValue(value) + } + } + + get value(): Value { + return this.state ? this.state!.value! : this.directValue! + } + + set value(value: Value) { + this.setValue(value) + } + + dispose() { + this.state?.dispose() + } + + private setValue(value: Value) { + if (this.isStatable(value)) { + this.setStateValue(observableProxy(value)) + } else { + if (this.state) { + throw new Error(`The property is already bound to a mutable state value`) + } + this.directValue = value + } + } + + private setStateValue(value: Value) { + if (!this.state) { + this.state = globalMutableState(value) + } + this.state!.value = value + } + + private isStatable(value: Value | undefined): boolean { + if (value instanceof ObservableClass) { + return value.getClassMetadata()?.isObservedV2(value) ?? false + } + return false + } +} + +export class PlainStructProperty implements SubscribedAbstractProperty { + private name: string + private readonly holder: StatableHolder + + constructor(name: string, value?: Value) { + this.name = name + this.holder = new StatableHolder(value) + } + + init(value: Value | undefined): void { + if (value != undefined) + this.holder.value = value + } + + info(): string { + return this.name + } + + get(): Value { + return this.holder.value! + } + + set(value: Value): void { + this.holder.value = value + } + + subscribe(listener: () => void): void { + } + + unsubscribe(listener: () => void): void { + } + + aboutToBeDeleted(): void { + this.holder.dispose() + } +} + +export class BuilderParamDecoratorProperty extends PlainStructProperty { + constructor(name: string, value?: Value) { + super(name, value) + } +} + +export class LinkDecoratorProperty implements SubscribedAbstractProperty { + private readonly name: string + private readonly watch: (() => void) | undefined + private property: SubscribedAbstractProperty | undefined = undefined + + constructor(name: string, watch?: () => void) { + this.name = name + this.watch = watch + } + + linkTo(maybeProperty: SubscribedAbstractProperty | Value | undefined): void { + if (!maybeProperty) throw new Error(`${this.name} must be linked with another property`) + const property = maybeProperty! // Improve: this is to workaround Any considered non-nulish + if (!(property instanceof SubscribedAbstractProperty)) throw new Error('Property must be passed, got') + if (this.property) throw new Error(`${this.name} is already linked with some property`) + this.property = property + if (this.watch) property.subscribe(this.watch!) + } + + info(): string { + return this.name + } + + get(): Value { + return this.property!.get() + } + + set(value: Value): void { + this.property!.set(value) + } + + subscribe(listener: () => void): void { + this.property!.unsubscribe(listener) + } + + unsubscribe(listener: () => void): void { + this.property!.unsubscribe(listener) + } + + aboutToBeDeleted(): void { + if (this.watch) this.property!.unsubscribe(this.watch!) + } +} + +export class StateDecoratorProperty implements SubscribedAbstractProperty { + private name: string + private state: MutableState | undefined = undefined + private listeners: Set<() => void> | undefined = undefined + + constructor(name: string, listener?: () => void) { + this.name = name + if (listener) this.subscribe(listener) + } + init(value?: Value, initial?: Value): void { + this.state = mutableState(observableProxy(value ?? (initial as Value))) + } + info(): string { + return this.name + } + get(): Value { + return this.state!.value + } + set(value: Value): void { + this.state!.value = observableProxy(value) + this.listeners?.forEach(notify) + } + subscribe(listener: () => void): void { + if (!this.listeners) this.listeners = new Set<() => void>() + this.listeners?.add(listener) + } + unsubscribe(listener: () => void): void { + this.listeners?.delete(listener) + } + aboutToBeDeleted(): void { + this.listeners?.clear() + } +} + +function notify(listener: () => void) { + listener() +} + +export class PropDecoratorProperty extends StateDecoratorProperty { + /* + _modified and _value needed for changes to be observable instantly, on the same recomputation value is being changed + */ + private _modified = false + private _value?: Value + + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value?: Value, initial?: Value): void { + super.init(value ? this.deepCopyOnUpdate(value) : undefined, initial) + } + get(): Value { + let value = super.get() // subscribe + if (this._modified) value = this._value as Value + return value + } + update(value?: Value): void { + this._modified = false + this._value = undefined + const scope = GlobalStateManager.instance.scope(0, 1) + const parameter = scope.param(0, value) + if (scope.unchanged) { + scope.cached + return + } + value = parameter.value // subscribe to update + if (value != undefined) { + const copy = this.deepCopyOnUpdate(value) + this._modified = true + this._value = copy + scheduleCallback(() => { this.set(copy) }) + } + scope.recache() + } + protected deepCopyOnUpdate(value: Value): Value { + return observableProxy(propDeepCopy(value)) + } +} + +export class ObjectLinkDecoratorProperty extends PropDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + protected deepCopyOnUpdate(value: Value): Value { + return value + } +} + +export class ProvideDecoratorProperty extends StateDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + provide(provideKey?: string): void { + GlobalStateManager.instance.namedState>(provideKey ?? this.info(), () => this) + } + checkOverrides(provideKey?: string): void { + const actualProvideKey = provideKey ?? this.info() + const state = GlobalStateManager.instance.stateBy(actualProvideKey, false) + if (state) { + throw new Error(`Variable "${actualProvideKey}" was already defined on the current page. ` + + `Use @Provide({allowOverride: "${actualProvideKey}"}) for explicit override or choose another variable name`) + } + } +} + +export class ConsumeDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(provideKey?: string): void { + this.linkTo(GlobalStateManager.instance.valueBy>(provideKey ?? this.info())) + } +} + +export class ConsumerDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(defaultValue: Value, provideKey?: string): void { + const provided = GlobalStateManager.instance.stateBy(provideKey ?? this.info()) + if (provided != undefined) { + this.linkTo(provided.value) + } else { + const state = new StateDecoratorProperty(this.info()) + state.init(undefined, defaultValue) + this.linkTo(state) + } + } +} + +export class StorageLinkDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value: Value, storageKey?: string): void { + this.linkTo(AppStorage.setAndLink(storageKey ?? this.info(), value)) + } +} + +export class LocalStorageLinkDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value: Value, storage: LocalStorage, storageKey?: string): void { + this.linkTo(storage.setAndLink(storageKey ?? this.info(), value)) + } +} + +export class StoragePropDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value: Value, storageKey?: string): void { + this.linkTo(AppStorage.setAndProp(storageKey ?? this.info(), value)) + } +} + +export class LocalStoragePropDecoratorProperty extends LinkDecoratorProperty { + constructor(name: string, listener?: () => void) { + super(name, listener) + } + init(value: Value, storage: LocalStorage, storageKey?: string): void { + this.linkTo(storage.setAndProp(storageKey ?? this.info(), value)) + } +} -- Gitee