From 71e7b215c2f712dcb924439c617c327c0bfce495 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 5 Feb 2025 18:39:04 +0300 Subject: [PATCH 1/2] Add AppStorage for arkts Signed-off-by: Anton Tarasov --- arkoala/arkui-common/src/arkts/Storage.ts | 358 +++++++++++++++++++++- 1 file changed, 356 insertions(+), 2 deletions(-) diff --git a/arkoala/arkui-common/src/arkts/Storage.ts b/arkoala/arkui-common/src/arkts/Storage.ts index 2d2abe098..41745604a 100644 --- a/arkoala/arkui-common/src/arkts/Storage.ts +++ b/arkoala/arkui-common/src/arkts/Storage.ts @@ -13,7 +13,361 @@ * limitations under the License. */ -import { GlobalStateManager, mutableState, MutableState } from "@koalaui/runtime" +import { GlobalStateManager, MutableState, StateManager } from "@koalaui/runtime" + +/** + * Defines the subscribed abstract property. + * @since 7 + * @systemapi + */ +export abstract class SubscribedAbstractProperty { + /** + * Returns the property name, + * e.g. let link = AppStorage.Link("foo") then link.info() == "foo" + * + * @returns { string } the property name if set or undefined + * @syscap SystemCapability.ArkUI.ArkUI.Full + * @crossplatform + * @since 10 + */ + info(): string { + return "TODO" + } + /** + * Called when data is obtained. + * @since 7 + * @systemapi + */ + abstract get(): T + + /** + * Called when data is created. + * @since 7 + * @systemapi + */ + abstract set(newValue: T): void + /** + * An app needs to call this function before the instance of SubscribedAbstractProperty + * goes out of scope / is subject to garbage collection. Its purpose is to unregister the + * variable from the two-way/one-way sync relationship that AppStorage/LocalStorage.link()/prop() + * and related functions create. + * + * @syscap SystemCapability.ArkUI.ArkUI.Full + * @crossplatform + * @since 10 + */ + abstract aboutToBeDeleted(): void +} + +class StorageEntry extends SubscribedAbstractProperty { + readonly state: MutableState + readonly mutable: boolean + + constructor(state: MutableState, mutable?: boolean) { + super() + this.state = state + this.mutable = mutable ?? true + } + + aboutToBeDeleted(): void { + // todo + } + + get(): T { + return this.state.value + } + + set(value: T): void { + if (this.mutable) this.state.value = value + } +} + +export class StorageMap { + private static _shared?: object + private readonly manager: StateManager = GlobalStateManager.instance + private readonly map: Map> = new Map>() + + static shared(): StorageMap { + if ((StorageMap._shared as StorageMap | undefined)?.manager != GlobalStateManager.instance) { + StorageMap._shared = new StorageMap() // recreate shared storage if state manager is changed + } + return StorageMap._shared as StorageMap + } + + keys(): IterableIterator { + return this.map.keys() + } + + get size(): number { + return this.map.size + } + + has(key: string): boolean { + return this.map.has(key) + } + + entry(key: string): StorageEntry | undefined { + return this.map.get(key) as StorageEntry | undefined + } + + get(key: string): T | undefined { + return this.entry(key)?.get() + } + + set(key: string, value: T, create?: boolean, mutable?: boolean): boolean { + const entry = this.entry(key) + if (entry) { + entry.set(value) + return true + } + if (create ?? true) { + this.create(key, value, mutable ?? true) + } + return false + } + + link(key: string, value: T): StorageEntry { + const entry = this.entry(key) + return entry ? entry : this.create(key, value) + } + + delete(key: string): boolean { + const entry = this.entry(key) + if (!entry) return false + entry.state.dispose() + return this.map.delete(key) + } + + clear(): boolean { + if (this.map.size == 0) return false + for (const entry of this.map.values()) { + entry.state.dispose() + } + this.map.clear() + return true + } + + private create(key: string, value: T, mutable?: boolean): StorageEntry { + const entry = new StorageEntry(this.manager.mutableState(value, true), mutable ?? true) + this.map.set(key, entry) + return entry + } +} export class LocalStorage { -} \ No newline at end of file +} + +/** + * Defines the AppStorage interface. + * @since 7 + */ +export class AppStorage { + /** + * Called when a link is set. + * @since 7 + * @deprecated since 10 + */ + static Link(propName: string): SubscribedAbstractProperty { + return AppStorage.link(propName) + } + /** + * Called when a link is set. + * @since 10 + */ + static link(propName: string): SubscribedAbstractProperty { + throw new Error("AppStorage.link is not implemented") + } + + /** + * Called when a hyperlink is set. + * @since 7 + * @deprecated since 10 + */ + static SetAndLink(propName: string, defaultValue: T): SubscribedAbstractProperty { + return AppStorage.setAndLink(propName, defaultValue) + } + /** + * Called when a hyperlink is set. + * @since 10 + */ + static setAndLink(propName: string, defaultValue: T): SubscribedAbstractProperty { + throw new Error("AppStorage.setAndLink is not implemented") + } + + /** + * Called when a property is set. + * @since 7 + * @deprecated since 10 + */ + static Prop(propName: string): SubscribedAbstractProperty { + return AppStorage.prop(propName) + } + /** + * Called when a property is set. + * @since 10 + */ + static prop(propName: string): SubscribedAbstractProperty { + throw new Error("AppStorage.prop is not implemented") + } + + /** + * Called when dynamic properties are set. + * @since 7 + * @deprecated since 10 + */ + static SetAndProp(propName: string, defaultValue: S): SubscribedAbstractProperty { + return AppStorage.setAndProp(propName, defaultValue) + } + /** + * Called when dynamic properties are set. + * @since 10 + */ + static setAndProp(propName: string, defaultValue: S): SubscribedAbstractProperty { + throw new Error("AppStorage.setAndProp is not implemented") + } + + /** + * Called when owning or not. + * @since 7 + * @deprecated since 10 + */ + static Has(propName: string): boolean { + return AppStorage.has(propName) + } + /** + * Called when owning or not. + * @since 10 + */ + static has(propName: string): boolean { + return StorageMap.shared().has(propName) + } + + /** + * Called when data is obtained. + * @since 7 + * @deprecated since 10 + */ + static Get(propName: string): T | undefined { + return AppStorage.get(propName) + } + /** + * Called when data is obtained. + * @since 10 + */ + static get(propName: string): T | undefined { + return StorageMap.shared().get(propName) + } + + /** + * Called when setting. + * @since 7 + * @deprecated since 10 + */ + static Set(propName: string, newValue: T): boolean { + return AppStorage.set(propName, newValue) + } + /** + * Called when setting. + * @since 10 + */ + static set(propName: string, newValue: T): boolean { + return StorageMap.shared().set(propName, newValue, false) + } + + /** + * Called when setting or creating. + * @since 7 + * @deprecated since 10 + */ + static SetOrCreate(propName: string, newValue: T): void { + AppStorage.setOrCreate(propName, newValue) + } + /** + * Called when setting or creating. + * @since 10 + */ + static setOrCreate(propName: string, newValue: T): void { + StorageMap.shared().set(propName, newValue, true) + } + + /** + * Called when a deletion is made. + * @since 7 + * @deprecated since 10 + */ + static Delete(propName: string): boolean { + return AppStorage.delete(propName) + } + /** + * Called when a deletion is made. + * @since 10 + */ + static delete(propName: string): boolean { + return StorageMap.shared().delete(propName) + } + + /** + * Called when a dictionary is sorted. + * @since 7 + * @deprecated since 10 + */ + static Keys(): IterableIterator { + return AppStorage.keys() + } + /** + * Called when a dictionary is sorted. + * @since 10 + */ + static keys(): IterableIterator { + return StorageMap.shared().keys() + } + + /** + * Called when a cleanup occurs. + * @since 7 + * @deprecated since 9 + * @useinstead AppStorage.Clear + */ + static staticClear(): boolean { + return StorageMap.shared().clear() + } + + /** + * Called when a cleanup occurs. + * @since 9 + * @deprecated since 10 + */ + static Clear(): boolean { + return AppStorage.clear() + } + /** + * Called when a cleanup occurs. + * @since 10 + */ + static clear(): boolean { + return StorageMap.shared().clear() + } + + /** + * Called when the data can be changed. + * @since 7 + */ + static IsMutable(propName: string): boolean { + return StorageMap.shared().entry(propName)?.mutable == true + } + + /** + * Called when you check how much data is stored. + * @since 7 + */ + static Size(): number { + return AppStorage.size() + } + /** + * Called when you check how much data is stored. + * @since 10 + */ + static size(): number { + return StorageMap.shared().size + } +} -- Gitee From 21cac6f30ace11e67b2d37126f411061c4f0adc9 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Thu, 6 Feb 2025 15:34:52 +0300 Subject: [PATCH 2/2] fix generic typing Signed-off-by: Anton Tarasov --- arkoala/arkui-common/src/arkts/Storage.ts | 170 ++++++++++++---------- 1 file changed, 95 insertions(+), 75 deletions(-) diff --git a/arkoala/arkui-common/src/arkts/Storage.ts b/arkoala/arkui-common/src/arkts/Storage.ts index 41745604a..252904241 100644 --- a/arkoala/arkui-common/src/arkts/Storage.ts +++ b/arkoala/arkui-common/src/arkts/Storage.ts @@ -13,7 +13,7 @@ * limitations under the License. */ -import { GlobalStateManager, MutableState, StateManager } from "@koalaui/runtime" +import { Disposable, GlobalStateManager, MutableState, StateManager } from "@koalaui/runtime" /** * Defines the subscribed abstract property. @@ -59,39 +59,51 @@ export abstract class SubscribedAbstractProperty { abstract aboutToBeDeleted(): void } -class StorageEntry extends SubscribedAbstractProperty { - readonly state: MutableState +interface Mutable { + readonly mutable: boolean +} + +class StorageEntry extends SubscribedAbstractProperty implements Disposable, Mutable { + readonly state: MutableState readonly mutable: boolean - constructor(state: MutableState, mutable?: boolean) { + constructor(state: MutableState, mutable?: boolean) { super() this.state = state this.mutable = mutable ?? true } + get disposed(): boolean { + return this.state.disposed + } + + dispose(): void { + this.state.dispose() + } + aboutToBeDeleted(): void { // todo } - get(): T { + get(): V { return this.state.value } - set(value: T): void { + set(value: V): void { if (this.mutable) this.state.value = value } } -export class StorageMap { - private static _shared?: object +export class StorageMap { + private static _shared?: StorageMap private readonly manager: StateManager = GlobalStateManager.instance - private readonly map: Map> = new Map>() + private readonly map: Map = new Map() - static shared(): StorageMap { - if ((StorageMap._shared as StorageMap | undefined)?.manager != GlobalStateManager.instance) { - StorageMap._shared = new StorageMap() // recreate shared storage if state manager is changed + static get shared(): StorageMap { + if (StorageMap._shared?.manager != GlobalStateManager.instance) { + StorageMap._shared = new StorageMap() // recreate shared storage if state manager is changed } - return StorageMap._shared as StorageMap + return StorageMap._shared as StorageMap } keys(): IterableIterator { @@ -106,16 +118,21 @@ export class StorageMap { return this.map.has(key) } - entry(key: string): StorageEntry | undefined { - return this.map.get(key) as StorageEntry | undefined + entry(key: string): StorageEntry | undefined { + return this.map.get(key) as StorageEntry | undefined + } + + entryObject(key: string): Object | undefined { + return this.map.get(key) } - get(key: string): T | undefined { - return this.entry(key)?.get() + get(key: string): V | undefined { + const entry = this.entry(key) + return entry?.get() } - set(key: string, value: T, create?: boolean, mutable?: boolean): boolean { - const entry = this.entry(key) + set(key: string, value: V, create?: boolean, mutable?: boolean): boolean { + const entry = this.entry(key) if (entry) { entry.set(value) return true @@ -126,35 +143,37 @@ export class StorageMap { return false } - link(key: string, value: T): StorageEntry { - const entry = this.entry(key) + link(key: string, value: V): StorageEntry { + const entry = this.entry(key) return entry ? entry : this.create(key, value) } delete(key: string): boolean { - const entry = this.entry(key) - if (!entry) return false - entry.state.dispose() + const entry = this.entryObject(key) + if (!entry) + return false; + (entry as Disposable).dispose(); return this.map.delete(key) } clear(): boolean { if (this.map.size == 0) return false for (const entry of this.map.values()) { - entry.state.dispose() + (entry as Disposable).dispose() } this.map.clear() return true } - private create(key: string, value: T, mutable?: boolean): StorageEntry { - const entry = new StorageEntry(this.manager.mutableState(value, true), mutable ?? true) + private create(key: string, value: V, mutable?: boolean): StorageEntry { + const entry = new StorageEntry(this.manager.mutableState(value, true), mutable ?? true) this.map.set(key, entry) return entry } } export class LocalStorage { + // todo } /** @@ -167,14 +186,14 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Link(propName: string): SubscribedAbstractProperty { - return AppStorage.link(propName) + static Link(propName: string): SubscribedAbstractProperty { + return AppStorage.link(propName) } /** * Called when a link is set. * @since 10 */ - static link(propName: string): SubscribedAbstractProperty { + static link(propName: string): SubscribedAbstractProperty { throw new Error("AppStorage.link is not implemented") } @@ -183,14 +202,14 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static SetAndLink(propName: string, defaultValue: T): SubscribedAbstractProperty { - return AppStorage.setAndLink(propName, defaultValue) + static SetAndLink(propName: string, defaultValue: V): SubscribedAbstractProperty { + return AppStorage.setAndLink(propName, defaultValue) } /** * Called when a hyperlink is set. * @since 10 */ - static setAndLink(propName: string, defaultValue: T): SubscribedAbstractProperty { + static setAndLink(propName: string, defaultValue: V): SubscribedAbstractProperty { throw new Error("AppStorage.setAndLink is not implemented") } @@ -199,14 +218,14 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Prop(propName: string): SubscribedAbstractProperty { - return AppStorage.prop(propName) + static Prop(propName: string): SubscribedAbstractProperty { + return AppStorage.prop(propName) } /** * Called when a property is set. * @since 10 */ - static prop(propName: string): SubscribedAbstractProperty { + static prop(propName: string): SubscribedAbstractProperty { throw new Error("AppStorage.prop is not implemented") } @@ -215,14 +234,14 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static SetAndProp(propName: string, defaultValue: S): SubscribedAbstractProperty { - return AppStorage.setAndProp(propName, defaultValue) + static SetAndProp(propName: string, defaultValue: V): SubscribedAbstractProperty { + return AppStorage.setAndProp(propName, defaultValue) } /** * Called when dynamic properties are set. * @since 10 */ - static setAndProp(propName: string, defaultValue: S): SubscribedAbstractProperty { + static setAndProp(propName: string, defaultValue: V): SubscribedAbstractProperty { throw new Error("AppStorage.setAndProp is not implemented") } @@ -231,15 +250,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Has(propName: string): boolean { - return AppStorage.has(propName) + static Has(propName: string): boolean { + return AppStorage.has(propName) } /** * Called when owning or not. * @since 10 */ - static has(propName: string): boolean { - return StorageMap.shared().has(propName) + static has(propName: string): boolean { + return StorageMap.shared.has(propName) } /** @@ -247,15 +266,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Get(propName: string): T | undefined { - return AppStorage.get(propName) + static Get(propName: string): V | undefined { + return AppStorage.get(propName) } /** * Called when data is obtained. * @since 10 */ - static get(propName: string): T | undefined { - return StorageMap.shared().get(propName) + static get(propName: string): V | undefined { + return StorageMap.shared.get(propName) } /** @@ -263,15 +282,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Set(propName: string, newValue: T): boolean { - return AppStorage.set(propName, newValue) + static Set(propName: string, newValue: V): boolean { + return AppStorage.set(propName, newValue) } /** * Called when setting. * @since 10 */ - static set(propName: string, newValue: T): boolean { - return StorageMap.shared().set(propName, newValue, false) + static set(propName: string, newValue: V): boolean { + return StorageMap.shared.set(propName, newValue, false) } /** @@ -279,15 +298,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static SetOrCreate(propName: string, newValue: T): void { - AppStorage.setOrCreate(propName, newValue) + static SetOrCreate(propName: string, newValue: V): void { + AppStorage.setOrCreate(propName, newValue) } /** * Called when setting or creating. * @since 10 */ - static setOrCreate(propName: string, newValue: T): void { - StorageMap.shared().set(propName, newValue, true) + static setOrCreate(propName: string, newValue: V): void { + StorageMap.shared.set(propName, newValue, true) } /** @@ -295,15 +314,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Delete(propName: string): boolean { - return AppStorage.delete(propName) + static Delete(propName: string): boolean { + return AppStorage.delete(propName) } /** * Called when a deletion is made. * @since 10 */ - static delete(propName: string): boolean { - return StorageMap.shared().delete(propName) + static delete(propName: string): boolean { + return StorageMap.shared.delete(propName) } /** @@ -311,15 +330,15 @@ export class AppStorage { * @since 7 * @deprecated since 10 */ - static Keys(): IterableIterator { - return AppStorage.keys() + static Keys(): IterableIterator { + return AppStorage.keys() } /** * Called when a dictionary is sorted. * @since 10 */ - static keys(): IterableIterator { - return StorageMap.shared().keys() + static keys(): IterableIterator { + return StorageMap.shared.keys() } /** @@ -328,8 +347,8 @@ export class AppStorage { * @deprecated since 9 * @useinstead AppStorage.Clear */ - static staticClear(): boolean { - return StorageMap.shared().clear() + static staticClear(): boolean { + return StorageMap.shared.clear() } /** @@ -337,37 +356,38 @@ export class AppStorage { * @since 9 * @deprecated since 10 */ - static Clear(): boolean { - return AppStorage.clear() + static Clear(): boolean { + return AppStorage.clear() } /** * Called when a cleanup occurs. * @since 10 */ - static clear(): boolean { - return StorageMap.shared().clear() + static clear(): boolean { + return StorageMap.shared.clear() } /** * Called when the data can be changed. * @since 7 */ - static IsMutable(propName: string): boolean { - return StorageMap.shared().entry(propName)?.mutable == true + static IsMutable(propName: string): boolean { + const entry = StorageMap.shared.entryObject(propName) + return entry ? (entry as Mutable).mutable : false } /** * Called when you check how much data is stored. * @since 7 */ - static Size(): number { - return AppStorage.size() + static Size(): number { + return AppStorage.size() } /** * Called when you check how much data is stored. * @since 10 */ - static size(): number { - return StorageMap.shared().size + static size(): number { + return StorageMap.shared.size } } -- Gitee