diff --git a/src/observable/map/BaseObservableMap.ts b/src/observable/map/BaseObservableMap.ts index f81a94f8..c82e531c 100644 --- a/src/observable/map/BaseObservableMap.ts +++ b/src/observable/map/BaseObservableMap.ts @@ -18,6 +18,7 @@ import {BaseObservable} from "../BaseObservable"; import {JoinedMap} from "./index"; import {MappedMap} from "./index"; import {FilteredMap} from "./index"; +import {BaseObservableValue, MapSizeObservableValue} from "../value/index"; import {SortedMapList} from "../list/SortedMapList.js"; @@ -66,19 +67,23 @@ export abstract class BaseObservableMap extends BaseObservable>(...otherMaps: Array): JoinedMap { return new JoinedMap([this as BaseObservableMap].concat(otherMaps)); - } + } - mapValues(mapper: Mapper, updater?: Updater): MappedMap { - return new MappedMap(this, mapper, updater); - } + mapValues(mapper: Mapper, updater?: Updater): MappedMap { + return new MappedMap(this, mapper, updater); + } - sortValues(comparator: Comparator): SortedMapList { - return new SortedMapList(this, comparator); - } + sortValues(comparator: Comparator): SortedMapList { + return new SortedMapList(this, comparator); + } - filterValues(filter: Filter): FilteredMap { - return new FilteredMap(this, filter); - } + filterValues(filter: Filter): FilteredMap { + return new FilteredMap(this, filter); + } + + observeSize(): BaseObservableValue { + return new MapSizeObservableValue(this); + } abstract [Symbol.iterator](): Iterator<[K, V]>; abstract get size(): number; @@ -94,4 +99,4 @@ export type Updater = (params: any, mappedValue?: MappedV, value?: V export type Comparator = (a: V, b: V) => number; -export type Filter = (v: V, k: K) => boolean; \ No newline at end of file +export type Filter = (v: V, k: K) => boolean; diff --git a/src/observable/value/MapSizeObservableValue.ts b/src/observable/value/MapSizeObservableValue.ts new file mode 100644 index 00000000..13bac604 --- /dev/null +++ b/src/observable/value/MapSizeObservableValue.ts @@ -0,0 +1,71 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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 {BaseObservableValue} from "./index"; +import {BaseObservableMap} from "../map/index"; +import type {SubscriptionHandle} from "../BaseObservable"; + +export class MapSizeObservableValue extends BaseObservableValue { + private subscription?: SubscriptionHandle; + + constructor(private readonly map: BaseObservableMap) + { + super(); + } + + onSubscribeFirst(): void { + this.subscription = this.map.subscribe({ + onAdd: (key: K, value: V) => { + this.emit(this.get()); + }, + onRemove: (key: K, value: V) => { + this.emit(this.get()); + }, + onUpdate: (key: K, value: V) => {}, + onReset: () => { + this.emit(this.get()); + }, + }); + } + + onUnsubscribeLast(): void { + this.subscription = this.subscription?.(); + } + + get(): number { + return this.map.size; + } +} + +import {ObservableMap} from "../map/index"; + +export function tests() { + return { + "emits update on add and remove": assert => { + const map = new ObservableMap(); + const size = new MapSizeObservableValue(map); + const updates: number[] = []; + size.subscribe(size => { + updates.push(size); + }); + map.add("hello", 1); + map.add("world", 2); + map.remove("world"); + map.remove("hello"); + assert.deepEqual(updates, [1, 2, 1, 0]); + } + }; +} diff --git a/src/observable/value/index.ts b/src/observable/value/index.ts index ceb53628..1b0f1347 100644 --- a/src/observable/value/index.ts +++ b/src/observable/value/index.ts @@ -12,4 +12,5 @@ export {EventObservableValue} from './EventObservableValue'; export {FlatMapObservableValue} from './FlatMapObservableValue'; export {PickMapObservableValue} from './PickMapObservableValue'; export {RetainedObservableValue} from './RetainedObservableValue'; +export {MapSizeObservableValue} from './MapSizeObservableValue'; export {ObservableValue} from './ObservableValue';