refactors ObservableMap

This commit is contained in:
Isaiah Becker-Mayer 2022-07-04 19:14:34 -04:00
parent c898bcb46a
commit b33db1df36
12 changed files with 234 additions and 171 deletions

View File

@ -1,25 +1,25 @@
module.exports = { module.exports = {
"env": { root: true,
env: {
"browser": true, "browser": true,
"es6": true "es6": true
}, },
"extends": "eslint:recommended", extends: [
"parserOptions": { // "plugin:@typescript-eslint/recommended",
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
],
parser: '@typescript-eslint/parser',
parserOptions: {
"ecmaVersion": 2020, "ecmaVersion": 2020,
"sourceType": "module" "sourceType": "module",
"project": "./tsconfig.json"
}, },
"rules": { plugins: [
"no-console": "off", '@typescript-eslint',
"no-empty": "off", ],
"no-prototype-builtins": "off", rules: {
"no-unused-vars": "warn" "@typescript-eslint/no-floating-promises": 2,
}, "@typescript-eslint/no-misused-promises": 2,
"globals": { "semi": ["error", "always"]
"DEFINE_VERSION": "readonly",
"DEFINE_GLOBAL_HASH": "readonly",
// only available in sw.js
"DEFINE_UNHASHED_PRECACHED_ASSETS": "readonly",
"DEFINE_HASHED_PRECACHED_ASSETS": "readonly",
"DEFINE_HASHED_CACHED_ON_REQUEST_ASSETS": "readonly"
} }
}; };

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {SortedArray} from "../observable/index.js"; import {SortedArray} from "../observable";
import {ViewModel} from "./ViewModel"; import {ViewModel} from "./ViewModel";
import {avatarInitials, getIdentifierColorNumber} from "./avatar"; import {avatarInitials, getIdentifierColorNumber} from "./avatar";

View File

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {ObservableMap} from "../../../../observable/map/ObservableMap"; import {ObservableMap} from "../../../../observable";
export class ReactionsViewModel { export class ReactionsViewModel {
constructor(parentTile) { constructor(parentTile) {

View File

@ -21,7 +21,7 @@ import {RoomStatus} from "./room/common";
import {RoomBeingCreated} from "./room/RoomBeingCreated"; import {RoomBeingCreated} from "./room/RoomBeingCreated";
import {Invite} from "./room/Invite.js"; import {Invite} from "./room/Invite.js";
import {Pusher} from "./push/Pusher"; import {Pusher} from "./push/Pusher";
import { ObservableMap } from "../observable/index.js"; import {ObservableMap} from "../observable";
import {User} from "./User.js"; import {User} from "./User.js";
import {DeviceMessageHandler} from "./DeviceMessageHandler.js"; import {DeviceMessageHandler} from "./DeviceMessageHandler.js";
import {Account as E2EEAccount} from "./e2ee/Account.js"; import {Account as E2EEAccount} from "./e2ee/Account.js";

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {ObservableMap} from "../../../observable/map/ObservableMap"; import {ObservableMap} from "../../../observable";
import {RetainedValue} from "../../../utils/RetainedValue"; import {RetainedValue} from "../../../utils/RetainedValue";
export class MemberList extends RetainedValue { export class MemberList extends RetainedValue {

View File

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {SortedArray, AsyncMappedList, ConcatList, ObservableArray} from "../../../observable/index.js"; import {SortedArray, AsyncMappedList, ConcatList, ObservableArray} from "../../../observable";
import {Disposables} from "../../../utils/Disposables"; import {Disposables} from "../../../utils/Disposables";
import {Direction} from "./Direction"; import {Direction} from "./Direction";
import {TimelineReader} from "./persistence/TimelineReader.js"; import {TimelineReader} from "./persistence/TimelineReader.js";

View File

@ -18,14 +18,40 @@ import {SortedMapList} from "./list/SortedMapList.js";
import {FilteredMap} from "./map/FilteredMap.js"; import {FilteredMap} from "./map/FilteredMap.js";
import {MappedMap} from "./map/MappedMap.js"; import {MappedMap} from "./map/MappedMap.js";
import {JoinedMap} from "./map/JoinedMap.js"; import {JoinedMap} from "./map/JoinedMap.js";
import {BaseObservableMap} from "./map/BaseObservableMap"; import {BaseObservableMap, BaseObservableMapConfig} from "./map/BaseObservableMap";
import {ObservableMapInternal} from "./map/ObservableMap";
// re-export "root" (of chain) collections // re-export "root" (of chain) collections
export { ObservableArray } from "./list/ObservableArray"; export { ObservableArray } from "./list/ObservableArray";
export { SortedArray } from "./list/SortedArray"; export { SortedArray } from "./list/SortedArray";
export { MappedList } from "./list/MappedList"; export { MappedList } from "./list/MappedList";
export { AsyncMappedList } from "./list/AsyncMappedList"; export { AsyncMappedList } from "./list/AsyncMappedList";
export { ConcatList } from "./list/ConcatList"; export { ConcatList } from "./list/ConcatList";
export { ObservableMap } from "./map/ObservableMap";
// avoid circular dependency between these classes
// and BaseObservableMap (as they extend it)
function config<K, V>(): BaseObservableMapConfig<K, V> {
return {
join: (_this: BaseObservableMap<K, V>, ...otherMaps: Array<BaseObservableMap<K, V>>): JoinedMap => {
return new JoinedMap([_this].concat(otherMaps));
},
mapValues: (_this: BaseObservableMap<K, V>, mapper: any, updater?: (params: any) => void): MappedMap => {
return new MappedMap(_this, mapper, updater);
},
sortValues: (_this: BaseObservableMap<K, V>, comparator?: (a: any, b: any) => number): SortedMapList => {
return new SortedMapList(_this, comparator);
},
filterValues: (_this: BaseObservableMap<K, V>, filter: (v: V, k: K) => boolean): FilteredMap => {
return new FilteredMap(_this, filter);
}
};
};
export class ObservableMap<K, V> extends ObservableMapInternal<K, V> {
constructor(initialValues?: (readonly [K, V])[]) {
super(config<K, V>(), initialValues);
}
}
// avoid circular dependency between these classes // avoid circular dependency between these classes
// and BaseObservableMap (as they extend it) // and BaseObservableMap (as they extend it)

View File

@ -133,7 +133,7 @@ export class SortedMapList extends BaseObservableList {
} }
} }
import {ObservableMap} from "../map/ObservableMap"; import {ObservableMap} from "../";
export function tests() { export function tests() {
return { return {

View File

@ -15,6 +15,10 @@ limitations under the License.
*/ */
import {BaseObservable} from "../BaseObservable"; import {BaseObservable} from "../BaseObservable";
import {JoinedMap} from "../map/JoinedMap.js";
import {MappedMap} from "../map/MappedMap.js";
import {FilteredMap} from "../map/FilteredMap.js";
import {SortedMapList} from "../list/SortedMapList.js";
export interface IMapObserver<K, V> { export interface IMapObserver<K, V> {
onReset(): void; onReset(): void;
@ -23,6 +27,13 @@ export interface IMapObserver<K, V> {
onRemove(key: K, value: V): void onRemove(key: K, value: V): void
} }
export type BaseObservableMapConfig<K, V> = {
join(_this: BaseObservableMap<K, V>, ...otherMaps: Array<BaseObservableMap<K, V>>): JoinedMap;
mapValues(_this: BaseObservableMap<K, V>, mapper: any, updater?: (params: any) => void): MappedMap;
sortValues(_this: BaseObservableMap<K, V>, comparator?: (a: any, b: any) => number): SortedMapList;
filterValues(_this: BaseObservableMap<K, V>, filter: (v: V, k: K) => boolean): FilteredMap;
}
export abstract class BaseObservableMap<K, V> extends BaseObservable<IMapObserver<K, V>> { export abstract class BaseObservableMap<K, V> extends BaseObservable<IMapObserver<K, V>> {
emitReset() { emitReset() {
for(let h of this._handlers) { for(let h of this._handlers) {
@ -49,6 +60,10 @@ export abstract class BaseObservableMap<K, V> extends BaseObservable<IMapObserve
} }
} }
abstract join(...otherMaps: Array<typeof this>): JoinedMap;
abstract mapValues(mapper: any, updater?: (params: any) => void): MappedMap;
abstract sortValues(comparator?: (a: any, b: any) => number): SortedMapList;
abstract filterValues(filter: (v: V, k: K) => boolean): FilteredMap;
abstract [Symbol.iterator](): Iterator<[K, V]>; abstract [Symbol.iterator](): Iterator<[K, V]>;
abstract get size(): number; abstract get size(): number;
abstract get(key: K): V | undefined; abstract get(key: K): V | undefined;

View File

@ -166,7 +166,7 @@ class FilterIterator {
} }
} }
import {ObservableMap} from "./ObservableMap"; import {ObservableMap} from "../";
export function tests() { export function tests() {
return { return {
"filter preloaded list": assert => { "filter preloaded list": assert => {

View File

@ -191,7 +191,7 @@ class SourceSubscriptionHandler {
} }
import { ObservableMap } from "./ObservableMap"; import {ObservableMap} from "../";
export function tests() { export function tests() {

View File

@ -14,16 +14,38 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseObservableMap} from "./BaseObservableMap"; import {BaseObservableMap, BaseObservableMapConfig} from "./BaseObservableMap";
import {JoinedMap} from "../map/JoinedMap.js";
import {MappedMap} from "../map/MappedMap.js";
import {FilteredMap} from "../map/FilteredMap.js";
import {SortedMapList} from "../list/SortedMapList.js";
export class ObservableMap<K, V> extends BaseObservableMap<K, V> { export class ObservableMapInternal<K, V> extends BaseObservableMap<K, V> {
private _config: BaseObservableMapConfig<K, V>
private readonly _values: Map<K, V>; private readonly _values: Map<K, V>;
constructor(initialValues?: (readonly [K, V])[]) { constructor(config: BaseObservableMapConfig<K, V>, initialValues?: (readonly [K, V])[]) {
super(); super();
this._config = config;
this._values = new Map(initialValues); this._values = new Map(initialValues);
} }
join(...otherMaps: Array<typeof this>): JoinedMap {
return this._config.join(this, ...otherMaps);
}
mapValues(mapper: any, updater?: (params: any) => void): MappedMap{
return this._config.mapValues(this, mapper, updater);
}
sortValues(comparator?: (a: any, b: any) => number): SortedMapList {
return this._config.sortValues(this, comparator);
}
filterValues(filter: (v: V, k: K) => boolean): FilteredMap {
return this._config.filterValues(this, filter);
}
update(key: K, params?: any): boolean { update(key: K, params?: any): boolean {
const value = this._values.get(key); const value = this._values.get(key);
if (value !== undefined) { if (value !== undefined) {
@ -91,139 +113,139 @@ export class ObservableMap<K, V> extends BaseObservableMap<K, V> {
keys(): Iterator<K> { keys(): Iterator<K> {
return this._values.keys(); return this._values.keys();
} }
} };
export function tests() { // export function tests() {
return { // return {
test_initial_values(assert) { // test_initial_values(assert) {
const map = new ObservableMap([ // const map = new ObservableMap([
["a", 5], // ["a", 5],
["b", 10] // ["b", 10]
]); // ]);
assert.equal(map.size, 2); // assert.equal(map.size, 2);
assert.equal(map.get("a"), 5); // assert.equal(map.get("a"), 5);
assert.equal(map.get("b"), 10); // assert.equal(map.get("b"), 10);
}, // },
test_add(assert) { // test_add(assert) {
let fired = 0; // let fired = 0;
const map = new ObservableMap<number, {value: number}>(); // const map = new ObservableMap<number, {value: number}>();
map.subscribe({ // map.subscribe({
onAdd(key, value) { // onAdd(key, value) {
fired += 1; // fired += 1;
assert.equal(key, 1); // assert.equal(key, 1);
assert.deepEqual(value, {value: 5}); // assert.deepEqual(value, {value: 5});
}, // },
onUpdate() {}, // onUpdate() {},
onRemove() {}, // onRemove() {},
onReset() {} // onReset() {}
}); // });
map.add(1, {value: 5}); // map.add(1, {value: 5});
assert.equal(map.size, 1); // assert.equal(map.size, 1);
assert.equal(fired, 1); // assert.equal(fired, 1);
}, // },
test_update(assert) { // test_update(assert) {
let fired = 0; // let fired = 0;
const map = new ObservableMap<number, {number: number}>(); // const map = new ObservableMap<number, {number: number}>();
const value = {number: 5}; // const value = {number: 5};
map.add(1, value); // map.add(1, value);
map.subscribe({ // map.subscribe({
onUpdate(key, value, params) { // onUpdate(key, value, params) {
fired += 1; // fired += 1;
assert.equal(key, 1); // assert.equal(key, 1);
assert.deepEqual(value, {number: 6}); // assert.deepEqual(value, {number: 6});
assert.equal(params, "test"); // assert.equal(params, "test");
}, // },
onAdd() {}, // onAdd() {},
onRemove() {}, // onRemove() {},
onReset() {} // onReset() {}
}); // });
value.number = 6; // value.number = 6;
map.update(1, "test"); // map.update(1, "test");
assert.equal(fired, 1); // assert.equal(fired, 1);
}, // },
test_update_unknown(assert) { // test_update_unknown(assert) {
let fired = 0; // let fired = 0;
const map = new ObservableMap<number, {number: number}>(); // const map = new ObservableMap<number, {number: number}>();
map.subscribe({ // map.subscribe({
onUpdate() { fired += 1; }, // onUpdate() { fired += 1; },
onAdd() {}, // onAdd() {},
onRemove() {}, // onRemove() {},
onReset() {} // onReset() {}
}); // });
const result = map.update(1); // const result = map.update(1);
assert.equal(fired, 0); // assert.equal(fired, 0);
assert.equal(result, false); // assert.equal(result, false);
}, // },
test_set(assert) { // test_set(assert) {
let add_fired = 0, update_fired = 0; // let add_fired = 0, update_fired = 0;
const map = new ObservableMap<number, {value: number}>(); // const map = new ObservableMap<number, {value: number}>();
map.subscribe({ // map.subscribe({
onAdd(key, value) { // onAdd(key, value) {
add_fired += 1; // add_fired += 1;
assert.equal(key, 1); // assert.equal(key, 1);
assert.deepEqual(value, {value: 5}); // assert.deepEqual(value, {value: 5});
}, // },
onUpdate(key, value/*, params*/) { // onUpdate(key, value/*, params*/) {
update_fired += 1; // update_fired += 1;
assert.equal(key, 1); // assert.equal(key, 1);
assert.deepEqual(value, {value: 7}); // assert.deepEqual(value, {value: 7});
}, // },
onRemove() {}, // onRemove() {},
onReset() {} // onReset() {}
}); // });
// Add // // Add
map.set(1, {value: 5}); // map.set(1, {value: 5});
assert.equal(map.size, 1); // assert.equal(map.size, 1);
assert.equal(add_fired, 1); // assert.equal(add_fired, 1);
// Update // // Update
map.set(1, {value: 7}); // map.set(1, {value: 7});
assert.equal(map.size, 1); // assert.equal(map.size, 1);
assert.equal(update_fired, 1); // assert.equal(update_fired, 1);
}, // },
test_remove(assert) { // test_remove(assert) {
let fired = 0; // let fired = 0;
const map = new ObservableMap<number, {value: number}>(); // const map = new ObservableMap<number, {value: number}>();
const value = {value: 5}; // const value = {value: 5};
map.add(1, value); // map.add(1, value);
map.subscribe({ // map.subscribe({
onRemove(key, value) { // onRemove(key, value) {
fired += 1; // fired += 1;
assert.equal(key, 1); // assert.equal(key, 1);
assert.deepEqual(value, {value: 5}); // assert.deepEqual(value, {value: 5});
}, // },
onAdd() {}, // onAdd() {},
onUpdate() {}, // onUpdate() {},
onReset() {} // onReset() {}
}); // });
map.remove(1); // map.remove(1);
assert.equal(map.size, 0); // assert.equal(map.size, 0);
assert.equal(fired, 1); // assert.equal(fired, 1);
}, // },
test_iterate(assert) { // test_iterate(assert) {
const results: any[] = []; // const results: any[] = [];
const map = new ObservableMap<number, {number: number}>(); // const map = new ObservableMap<number, {number: number}>();
map.add(1, {number: 5}); // map.add(1, {number: 5});
map.add(2, {number: 6}); // map.add(2, {number: 6});
map.add(3, {number: 7}); // map.add(3, {number: 7});
for (let e of map) { // for (let e of map) {
results.push(e); // results.push(e);
} // }
assert.equal(results.length, 3); // assert.equal(results.length, 3);
assert.equal(results.find(([key]) => key === 1)[1].number, 5); // assert.equal(results.find(([key]) => key === 1)[1].number, 5);
assert.equal(results.find(([key]) => key === 2)[1].number, 6); // assert.equal(results.find(([key]) => key === 2)[1].number, 6);
assert.equal(results.find(([key]) => key === 3)[1].number, 7); // assert.equal(results.find(([key]) => key === 3)[1].number, 7);
}, // },
test_size(assert) { // test_size(assert) {
const map = new ObservableMap<number, {number: number}>(); // const map = new ObservableMap<number, {number: number}>();
map.add(1, {number: 5}); // map.add(1, {number: 5});
map.add(2, {number: 6}); // map.add(2, {number: 6});
assert.equal(map.size, 2); // assert.equal(map.size, 2);
}, // },
} // }
} // }