Merge pull request #606 from vector-im/bwindels/typescript-observable-2

Typescript conversion of yet more observables
This commit is contained in:
Bruno Windels 2021-11-30 17:09:16 +01:00 committed by GitHub
commit 08314bd4b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 181 additions and 168 deletions

View file

@ -188,7 +188,7 @@ import {NullLogItem, NullLogger} from "../../../../logging/NullLogger";
import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js"; import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js";
// other imports // other imports
import {BaseMessageTile} from "./tiles/BaseMessageTile.js"; import {BaseMessageTile} from "./tiles/BaseMessageTile.js";
import {MappedList} from "../../../../observable/list/MappedList.js"; import {MappedList} from "../../../../observable/list/MappedList";
import {ObservableValue} from "../../../../observable/ObservableValue"; import {ObservableValue} from "../../../../observable/ObservableValue";
import {PowerLevels} from "../../../../matrix/room/PowerLevels.js"; import {PowerLevels} from "../../../../matrix/room/PowerLevels.js";

View file

@ -253,7 +253,7 @@ export class TilesCollection extends BaseObservableList {
} }
} }
import {ObservableArray} from "../../../../observable/list/ObservableArray.js"; import {ObservableArray} from "../../../../observable/list/ObservableArray";
import {UpdateAction} from "./UpdateAction.js"; import {UpdateAction} from "./UpdateAction.js";
export function tests() { export function tests() {

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/list/SortedArray.js"; import {SortedArray} from "../../../observable/list/SortedArray";
import {ConnectionError} from "../../error.js"; import {ConnectionError} from "../../error.js";
import {PendingEvent, SendStatus} from "./PendingEvent.js"; import {PendingEvent, SendStatus} from "./PendingEvent.js";
import {makeTxnId, isTxnId} from "../../common.js"; import {makeTxnId, isTxnId} from "../../common.js";

View file

@ -20,11 +20,11 @@ import {MappedMap} from "./map/MappedMap.js";
import {JoinedMap} from "./map/JoinedMap.js"; import {JoinedMap} from "./map/JoinedMap.js";
import {BaseObservableMap} from "./map/BaseObservableMap.js"; import {BaseObservableMap} from "./map/BaseObservableMap.js";
// re-export "root" (of chain) collections // re-export "root" (of chain) collections
export { ObservableArray } from "./list/ObservableArray.js"; export { ObservableArray } from "./list/ObservableArray";
export { SortedArray } from "./list/SortedArray.js"; export { SortedArray } from "./list/SortedArray";
export { MappedList } from "./list/MappedList.js"; export { MappedList } from "./list/MappedList";
export { AsyncMappedList } from "./list/AsyncMappedList.js"; export { AsyncMappedList } from "./list/AsyncMappedList";
export { ConcatList } from "./list/ConcatList.js"; export { ConcatList } from "./list/ConcatList";
export { ObservableMap } from "./map/ObservableMap.js"; export { ObservableMap } from "./map/ObservableMap.js";
// avoid circular dependency between these classes // avoid circular dependency between these classes

View file

@ -15,15 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; import {IListObserver} from "./BaseObservableList";
import {BaseMappedList, Mapper, Updater, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList";
export class AsyncMappedList extends BaseMappedList { export class AsyncMappedList<F,T> extends BaseMappedList<F,T,Promise<T>> implements IListObserver<F> {
constructor(sourceList, mapper, updater, removeCallback) { private _eventQueue: AsyncEvent<F>[] | null = null;
super(sourceList, mapper, updater, removeCallback); private _flushing: boolean = false;
this._eventQueue = null;
}
onSubscribeFirst() { onSubscribeFirst(): void {
this._sourceUnsubscribe = this._sourceList.subscribe(this); this._sourceUnsubscribe = this._sourceList.subscribe(this);
this._eventQueue = []; this._eventQueue = [];
this._mappedValues = []; this._mappedValues = [];
@ -35,122 +34,112 @@ export class AsyncMappedList extends BaseMappedList {
this._flush(); this._flush();
} }
async _flush() { async _flush(): Promise<void> {
if (this._flushing) { if (this._flushing) {
return; return;
} }
this._flushing = true; this._flushing = true;
try { try {
while (this._eventQueue.length) { while (this._eventQueue!.length) {
const event = this._eventQueue.shift(); const event = this._eventQueue!.shift();
await event.run(this); await event!.run(this);
} }
} finally { } finally {
this._flushing = false; this._flushing = false;
} }
} }
onReset() { onReset(): void {
if (this._eventQueue) { if (this._eventQueue) {
this._eventQueue.push(new ResetEvent()); this._eventQueue.push(new ResetEvent());
this._flush(); this._flush();
} }
} }
onAdd(index, value) { onAdd(index: number, value: F): void {
if (this._eventQueue) { if (this._eventQueue) {
this._eventQueue.push(new AddEvent(index, value)); this._eventQueue.push(new AddEvent(index, value));
this._flush(); this._flush();
} }
} }
onUpdate(index, value, params) { onUpdate(index: number, value: F, params: any): void {
if (this._eventQueue) { if (this._eventQueue) {
this._eventQueue.push(new UpdateEvent(index, value, params)); this._eventQueue.push(new UpdateEvent(index, value, params));
this._flush(); this._flush();
} }
} }
onRemove(index) { onRemove(index: number): void {
if (this._eventQueue) { if (this._eventQueue) {
this._eventQueue.push(new RemoveEvent(index)); this._eventQueue.push(new RemoveEvent(index));
this._flush(); this._flush();
} }
} }
onMove(fromIdx, toIdx) { onMove(fromIdx: number, toIdx: number): void {
if (this._eventQueue) { if (this._eventQueue) {
this._eventQueue.push(new MoveEvent(fromIdx, toIdx)); this._eventQueue.push(new MoveEvent(fromIdx, toIdx));
this._flush(); this._flush();
} }
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
this._sourceUnsubscribe(); this._sourceUnsubscribe!();
this._eventQueue = null; this._eventQueue = null;
this._mappedValues = null; this._mappedValues = null;
} }
} }
class AddEvent { type AsyncEvent<F> = AddEvent<F> | UpdateEvent<F> | RemoveEvent<F> | MoveEvent<F> | ResetEvent<F>
constructor(index, value) {
this.index = index;
this.value = value;
}
async run(list) { class AddEvent<F> {
constructor(public index: number, public value: F) {}
async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
const mappedValue = await list._mapper(this.value); const mappedValue = await list._mapper(this.value);
runAdd(list, this.index, mappedValue); runAdd(list, this.index, mappedValue);
} }
} }
class UpdateEvent { class UpdateEvent<F> {
constructor(index, value, params) { constructor(public index: number, public value: F, public params: any) {}
this.index = index;
this.value = value;
this.params = params;
}
async run(list) { async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
runUpdate(list, this.index, this.value, this.params); runUpdate(list, this.index, this.value, this.params);
} }
} }
class RemoveEvent { class RemoveEvent<F> {
constructor(index) { constructor(public index: number) {}
this.index = index;
}
async run(list) { async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
runRemove(list, this.index); runRemove(list, this.index);
} }
} }
class MoveEvent { class MoveEvent<F> {
constructor(fromIdx, toIdx) { constructor(public fromIdx: number, public toIdx: number) {}
this.fromIdx = fromIdx;
this.toIdx = toIdx;
}
async run(list) { async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
runMove(list, this.fromIdx, this.toIdx); runMove(list, this.fromIdx, this.toIdx);
} }
} }
class ResetEvent { class ResetEvent<F> {
async run(list) { async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
runReset(list); runReset(list);
} }
} }
import {ObservableArray} from "./ObservableArray.js"; import {ObservableArray} from "./ObservableArray";
import {ListObserver} from "../../mocks/ListObserver.js"; import {ListObserver} from "../../mocks/ListObserver.js";
export function tests() { export function tests() {
return { return {
"events are emitted in order": async assert => { "events are emitted in order": async assert => {
const double = n => n * n; const double = n => n * n;
const source = new ObservableArray(); const source = new ObservableArray<number>();
const mapper = new AsyncMappedList(source, async n => { const mapper = new AsyncMappedList(source, async n => {
await new Promise(r => setTimeout(r, n)); await new Promise(r => setTimeout(r, n));
return {n: double(n)}; return {n: double(n)};

View file

@ -21,15 +21,15 @@ import {findAndUpdateInArray} from "./common";
export type Mapper<F,T> = (value: F) => T export type Mapper<F,T> = (value: F) => T
export type Updater<F,T> = (mappedValue: T, params: any, value: F) => void; export type Updater<F,T> = (mappedValue: T, params: any, value: F) => void;
export class BaseMappedList<F,T> extends BaseObservableList<T> { export class BaseMappedList<F,T,R = T> extends BaseObservableList<T> {
protected _sourceList: BaseObservableList<F>; protected _sourceList: BaseObservableList<F>;
protected _sourceUnsubscribe: (() => void) | null = null; protected _sourceUnsubscribe: (() => void) | null = null;
_mapper: Mapper<F,T>; _mapper: Mapper<F,R>;
_updater: Updater<F,T>; _updater?: Updater<F,T>;
_removeCallback?: (value: T) => void; _removeCallback?: (value: T) => void;
_mappedValues: T[] | null = null; _mappedValues: T[] | null = null;
constructor(sourceList: BaseObservableList<F>, mapper: Mapper<F,T>, updater: Updater<F,T>, removeCallback?: (value: T) => void) { constructor(sourceList: BaseObservableList<F>, mapper: Mapper<F,R>, updater?: Updater<F,T>, removeCallback?: (value: T) => void) {
super(); super();
this._sourceList = sourceList; this._sourceList = sourceList;
this._mapper = mapper; this._mapper = mapper;
@ -50,12 +50,12 @@ export class BaseMappedList<F,T> extends BaseObservableList<T> {
} }
} }
export function runAdd<F,T>(list: BaseMappedList<F,T>, index: number, mappedValue: T): void { export function runAdd<F,T,R>(list: BaseMappedList<F,T,R>, index: number, mappedValue: T): void {
list._mappedValues!.splice(index, 0, mappedValue); list._mappedValues!.splice(index, 0, mappedValue);
list.emitAdd(index, mappedValue); list.emitAdd(index, mappedValue);
} }
export function runUpdate<F,T>(list: BaseMappedList<F,T>, index: number, value: F, params: any): void { export function runUpdate<F,T,R>(list: BaseMappedList<F,T,R>, index: number, value: F, params: any): void {
const mappedValue = list._mappedValues![index]; const mappedValue = list._mappedValues![index];
if (list._updater) { if (list._updater) {
list._updater(mappedValue, params, value); list._updater(mappedValue, params, value);
@ -63,7 +63,7 @@ export function runUpdate<F,T>(list: BaseMappedList<F,T>, index: number, value:
list.emitUpdate(index, mappedValue, params); list.emitUpdate(index, mappedValue, params);
} }
export function runRemove<F,T>(list: BaseMappedList<F,T>, index: number): void { export function runRemove<F,T,R>(list: BaseMappedList<F,T,R>, index: number): void {
const mappedValue = list._mappedValues![index]; const mappedValue = list._mappedValues![index];
list._mappedValues!.splice(index, 1); list._mappedValues!.splice(index, 1);
if (list._removeCallback) { if (list._removeCallback) {
@ -72,14 +72,14 @@ export function runRemove<F,T>(list: BaseMappedList<F,T>, index: number): void {
list.emitRemove(index, mappedValue); list.emitRemove(index, mappedValue);
} }
export function runMove<F,T>(list: BaseMappedList<F,T>, fromIdx: number, toIdx: number): void { export function runMove<F,T,R>(list: BaseMappedList<F,T,R>, fromIdx: number, toIdx: number): void {
const mappedValue = list._mappedValues![fromIdx]; const mappedValue = list._mappedValues![fromIdx];
list._mappedValues!.splice(fromIdx, 1); list._mappedValues!.splice(fromIdx, 1);
list._mappedValues!.splice(toIdx, 0, mappedValue); list._mappedValues!.splice(toIdx, 0, mappedValue);
list.emitMove(fromIdx, toIdx, mappedValue); list.emitMove(fromIdx, toIdx, mappedValue);
} }
export function runReset<F,T>(list: BaseMappedList<F,T>): void { export function runReset<F,T,R>(list: BaseMappedList<F,T,R>): void {
list._mappedValues = []; list._mappedValues = [];
list.emitReset(); list.emitReset();
} }

View file

@ -24,7 +24,18 @@ export interface IListObserver<T> {
onMove(from: number, to: number, value: T, list: BaseObservableList<T>): void onMove(from: number, to: number, value: T, list: BaseObservableList<T>): void
} }
export abstract class BaseObservableList<T> extends BaseObservable<IListObserver<T>> { export function defaultObserverWith<T>(overrides: { [key in keyof IListObserver<T>]?: IListObserver<T>[key] }): IListObserver<T> {
const defaults = {
onReset(){},
onAdd(){},
onUpdate(){},
onRemove(){},
onMove(){},
}
return Object.assign(defaults, overrides);
}
export abstract class BaseObservableList<T> extends BaseObservable<IListObserver<T>> implements Iterable<T> {
emitReset() { emitReset() {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onReset(this); h.onReset(this);
@ -38,7 +49,7 @@ export abstract class BaseObservableList<T> extends BaseObservable<IListObserver
} }
} }
emitUpdate(index: number, value: T, params: any): void { emitUpdate(index: number, value: T, params?: any): void {
for(let h of this._handlers) { for(let h of this._handlers) {
h.onUpdate(index, value, params, this); h.onUpdate(index, value, params, this);
} }
@ -58,6 +69,6 @@ export abstract class BaseObservableList<T> extends BaseObservable<IListObserver
} }
} }
abstract [Symbol.iterator](): IterableIterator<T>; abstract [Symbol.iterator](): Iterator<T>;
abstract get length(): number; abstract get length(): number;
} }

View file

@ -14,16 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {BaseObservableList} from "./BaseObservableList"; import {BaseObservableList, IListObserver} from "./BaseObservableList";
export class ConcatList extends BaseObservableList { export class ConcatList<T> extends BaseObservableList<T> implements IListObserver<T> {
constructor(...sourceLists) { protected _sourceLists: BaseObservableList<T>[];
protected _sourceUnsubscribes: (() => void)[] | null = null;
constructor(...sourceLists: BaseObservableList<T>[]) {
super(); super();
this._sourceLists = sourceLists; this._sourceLists = sourceLists;
this._sourceUnsubscribes = null;
} }
_offsetForSource(sourceList) { _offsetForSource(sourceList: BaseObservableList<T>): number {
const listIdx = this._sourceLists.indexOf(sourceList); const listIdx = this._sourceLists.indexOf(sourceList);
let offset = 0; let offset = 0;
for (let i = 0; i < listIdx; ++i) { for (let i = 0; i < listIdx; ++i) {
@ -32,17 +34,17 @@ export class ConcatList extends BaseObservableList {
return offset; return offset;
} }
onSubscribeFirst() { onSubscribeFirst(): void {
this._sourceUnsubscribes = this._sourceLists.map(sourceList => sourceList.subscribe(this)); this._sourceUnsubscribes = this._sourceLists.map(sourceList => sourceList.subscribe(this));
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
for (const sourceUnsubscribe of this._sourceUnsubscribes) { for (const sourceUnsubscribe of this._sourceUnsubscribes!) {
sourceUnsubscribe(); sourceUnsubscribe();
} }
} }
onReset() { onReset(): void {
// TODO: not ideal if other source lists are large // TODO: not ideal if other source lists are large
// but working impl for now // but working impl for now
// reset, and // reset, and
@ -54,11 +56,11 @@ export class ConcatList extends BaseObservableList {
} }
} }
onAdd(index, value, sourceList) { onAdd(index: number, value: T, sourceList: BaseObservableList<T>): void {
this.emitAdd(this._offsetForSource(sourceList) + index, value); this.emitAdd(this._offsetForSource(sourceList) + index, value);
} }
onUpdate(index, value, params, sourceList) { onUpdate(index: number, value: T, params: any, sourceList: BaseObservableList<T>): void {
// if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it
// as we are not supposed to call `length` on any uninitialized list // as we are not supposed to call `length` on any uninitialized list
if (!this._sourceUnsubscribes) { if (!this._sourceUnsubscribes) {
@ -67,16 +69,16 @@ export class ConcatList extends BaseObservableList {
this.emitUpdate(this._offsetForSource(sourceList) + index, value, params); this.emitUpdate(this._offsetForSource(sourceList) + index, value, params);
} }
onRemove(index, value, sourceList) { onRemove(index: number, value: T, sourceList: BaseObservableList<T>): void {
this.emitRemove(this._offsetForSource(sourceList) + index, value); this.emitRemove(this._offsetForSource(sourceList) + index, value);
} }
onMove(fromIdx, toIdx, value, sourceList) { onMove(fromIdx: number, toIdx: number, value: T, sourceList: BaseObservableList<T>): void {
const offset = this._offsetForSource(sourceList); const offset = this._offsetForSource(sourceList);
this.emitMove(offset + fromIdx, offset + toIdx, value); this.emitMove(offset + fromIdx, offset + toIdx, value);
} }
get length() { get length(): number {
let len = 0; let len = 0;
for (let i = 0; i < this._sourceLists.length; ++i) { for (let i = 0; i < this._sourceLists.length; ++i) {
len += this._sourceLists[i].length; len += this._sourceLists[i].length;
@ -104,7 +106,8 @@ export class ConcatList extends BaseObservableList {
} }
} }
import {ObservableArray} from "./ObservableArray.js"; import {ObservableArray} from "./ObservableArray";
import {defaultObserverWith} from "./BaseObservableList";
export async function tests() { export async function tests() {
return { return {
test_length(assert) { test_length(assert) {
@ -133,13 +136,13 @@ export async function tests() {
const list2 = new ObservableArray([11, 12, 13]); const list2 = new ObservableArray([11, 12, 13]);
const all = new ConcatList(list1, list2); const all = new ConcatList(list1, list2);
let fired = false; let fired = false;
all.subscribe({ all.subscribe(defaultObserverWith({
onAdd(index, value) { onAdd(index, value) {
fired = true; fired = true;
assert.equal(index, 4); assert.equal(index, 4);
assert.equal(value, 11.5); assert.equal(value, 11.5);
} }
}); }));
list2.insert(1, 11.5); list2.insert(1, 11.5);
assert(fired); assert(fired);
}, },
@ -148,13 +151,13 @@ export async function tests() {
const list2 = new ObservableArray([11, 12, 13]); const list2 = new ObservableArray([11, 12, 13]);
const all = new ConcatList(list1, list2); const all = new ConcatList(list1, list2);
let fired = false; let fired = false;
all.subscribe({ all.subscribe(defaultObserverWith({
onUpdate(index, value) { onUpdate(index, value) {
fired = true; fired = true;
assert.equal(index, 4); assert.equal(index, 4);
assert.equal(value, 10); assert.equal(value, 10);
} }
}); }));
list2.emitUpdate(1, 10); list2.emitUpdate(1, 10);
assert(fired); assert(fired);
}, },

View file

@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {IListObserver} from "./BaseObservableList";
import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList"; import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList";
export class MappedList extends BaseMappedList { export class MappedList<F,T> extends BaseMappedList<F,T> implements IListObserver<F> {
onSubscribeFirst() { onSubscribeFirst() {
this._sourceUnsubscribe = this._sourceList.subscribe(this); this._sourceUnsubscribe = this._sourceList.subscribe(this);
this._mappedValues = []; this._mappedValues = [];
@ -26,16 +27,16 @@ export class MappedList extends BaseMappedList {
} }
} }
onReset() { onReset(): void {
runReset(this); runReset(this);
} }
onAdd(index, value) { onAdd(index: number, value: F): void {
const mappedValue = this._mapper(value); const mappedValue = this._mapper(value);
runAdd(this, index, mappedValue); runAdd(this, index, mappedValue);
} }
onUpdate(index, value, params) { onUpdate(index: number, value: F, params: any): void {
// if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it // if an update is emitted while calling source.subscribe() from onSubscribeFirst, ignore it
if (!this._mappedValues) { if (!this._mappedValues) {
return; return;
@ -43,24 +44,25 @@ export class MappedList extends BaseMappedList {
runUpdate(this, index, value, params); runUpdate(this, index, value, params);
} }
onRemove(index) { onRemove(index: number): void {
runRemove(this, index); runRemove(this, index);
} }
onMove(fromIdx, toIdx) { onMove(fromIdx: number, toIdx: number): void {
runMove(this, fromIdx, toIdx); runMove(this, fromIdx, toIdx);
} }
onUnsubscribeLast() { onUnsubscribeLast(): void {
this._sourceUnsubscribe(); this._sourceUnsubscribe!();
} }
} }
import {ObservableArray} from "./ObservableArray.js"; import {ObservableArray} from "./ObservableArray";
import {BaseObservableList} from "./BaseObservableList"; import {BaseObservableList} from "./BaseObservableList";
import {defaultObserverWith} from "./BaseObservableList";
export async function tests() { export async function tests() {
class MockList extends BaseObservableList { class MockList extends BaseObservableList<number> {
get length() { get length() {
return 0; return 0;
} }
@ -74,26 +76,26 @@ export async function tests() {
const source = new MockList(); const source = new MockList();
const mapped = new MappedList(source, n => {return {n: n*n};}); const mapped = new MappedList(source, n => {return {n: n*n};});
let fired = false; let fired = false;
const unsubscribe = mapped.subscribe({ const unsubscribe = mapped.subscribe(defaultObserverWith({
onAdd(idx, value) { onAdd(idx, value) {
fired = true; fired = true;
assert.equal(idx, 0); assert.equal(idx, 0);
assert.equal(value.n, 36); assert.equal(value.n, 36);
} }
}); }));
source.emitAdd(0, 6); source.emitAdd(0, 6);
assert(fired); assert(fired);
unsubscribe(); unsubscribe();
}, },
test_update(assert) { test_update(assert) {
const source = new MockList(); const source = new MockList();
const mapped = new MappedList( const mapped = new MappedList<number, { n: number, m?: number }>(
source, source,
n => {return {n: n*n};}, n => {return {n: n*n};},
(o, p, n) => o.m = n*n (o, p, n) => o.m = n*n
); );
let fired = false; let fired = false;
const unsubscribe = mapped.subscribe({ const unsubscribe = mapped.subscribe(defaultObserverWith({
onAdd() {}, onAdd() {},
onUpdate(idx, value) { onUpdate(idx, value) {
fired = true; fired = true;
@ -101,7 +103,7 @@ export async function tests() {
assert.equal(value.n, 36); assert.equal(value.n, 36);
assert.equal(value.m, 49); assert.equal(value.m, 49);
} }
}); }));
source.emitAdd(0, 6); source.emitAdd(0, 6);
source.emitUpdate(0, 7); source.emitUpdate(0, 7);
assert(fired); assert(fired);
@ -113,9 +115,9 @@ export async function tests() {
source, source,
n => {return n*n;} n => {return n*n;}
); );
mapped.subscribe({ mapped.subscribe(defaultObserverWith({
onUpdate() { assert.fail(); } onUpdate() { assert.fail(); }
}); }));
assert.equal(mapped.findAndUpdate( assert.equal(mapped.findAndUpdate(
n => n === 100, n => n === 100,
() => assert.fail() () => assert.fail()
@ -127,9 +129,9 @@ export async function tests() {
source, source,
n => {return n*n;} n => {return n*n;}
); );
mapped.subscribe({ mapped.subscribe(defaultObserverWith({
onUpdate() { assert.fail(); } onUpdate() { assert.fail(); }
}); }));
let fired = false; let fired = false;
assert.equal(mapped.findAndUpdate( assert.equal(mapped.findAndUpdate(
n => n === 9, n => n === 9,
@ -148,14 +150,14 @@ export async function tests() {
n => {return n*n;} n => {return n*n;}
); );
let fired = false; let fired = false;
mapped.subscribe({ mapped.subscribe(defaultObserverWith({
onUpdate(idx, n, params) { onUpdate(idx, n, params) {
assert.equal(idx, 1); assert.equal(idx, 1);
assert.equal(n, 9); assert.equal(n, 9);
assert.equal(params, "param"); assert.equal(params, "param");
fired = true; fired = true;
} }
}); }));
assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true); assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true);
assert.equal(fired, true); assert.equal(fired, true);
}, },

View file

@ -16,35 +16,37 @@ limitations under the License.
import {BaseObservableList} from "./BaseObservableList"; import {BaseObservableList} from "./BaseObservableList";
export class ObservableArray extends BaseObservableList { export class ObservableArray<T> extends BaseObservableList<T> {
constructor(initialValues = []) { private _items: T[];
constructor(initialValues: T[] = []) {
super(); super();
this._items = initialValues; this._items = initialValues;
} }
append(item) { append(item: T): void {
this._items.push(item); this._items.push(item);
this.emitAdd(this._items.length - 1, item); this.emitAdd(this._items.length - 1, item);
} }
remove(idx) { remove(idx: number): void {
const [item] = this._items.splice(idx, 1); const [item] = this._items.splice(idx, 1);
this.emitRemove(idx, item); this.emitRemove(idx, item);
} }
insertMany(idx, items) { insertMany(idx: number, items: T[]): void {
for(let item of items) { for(let item of items) {
this.insert(idx, item); this.insert(idx, item);
idx += 1; idx += 1;
} }
} }
insert(idx, item) { insert(idx: number, item: T): void {
this._items.splice(idx, 0, item); this._items.splice(idx, 0, item);
this.emitAdd(idx, item); this.emitAdd(idx, item);
} }
move(fromIdx, toIdx) { move(fromIdx: number, toIdx: number): void {
if (fromIdx < this._items.length && toIdx < this._items.length) { if (fromIdx < this._items.length && toIdx < this._items.length) {
const [item] = this._items.splice(fromIdx, 1); const [item] = this._items.splice(fromIdx, 1);
this._items.splice(toIdx, 0, item); this._items.splice(toIdx, 0, item);
@ -52,24 +54,24 @@ export class ObservableArray extends BaseObservableList {
} }
} }
update(idx, item, params = null) { update(idx: number, item: T, params: any = null): void {
if (idx < this._items.length) { if (idx < this._items.length) {
this._items[idx] = item; this._items[idx] = item;
this.emitUpdate(idx, item, params); this.emitUpdate(idx, item, params);
} }
} }
get array() { get array(): Readonly<T[]> {
return this._items; return this._items;
} }
at(idx) { at(idx: number): T | undefined {
if (this._items && idx >= 0 && idx < this._items.length) { if (this._items && idx >= 0 && idx < this._items.length) {
return this._items[idx]; return this._items[idx];
} }
} }
get length() { get length(): number {
return this._items.length; return this._items.length;
} }

View file

@ -18,18 +18,20 @@ import {BaseObservableList} from "./BaseObservableList";
import {sortedIndex} from "../../utils/sortedIndex"; import {sortedIndex} from "../../utils/sortedIndex";
import {findAndUpdateInArray} from "./common"; import {findAndUpdateInArray} from "./common";
export class SortedArray extends BaseObservableList { export class SortedArray<T> extends BaseObservableList<T> {
constructor(comparator) { private _comparator: (left: T, right: T) => number;
private _items: T[] = [];
constructor(comparator: (left: T, right: T) => number) {
super(); super();
this._comparator = comparator; this._comparator = comparator;
this._items = [];
} }
setManyUnsorted(items) { setManyUnsorted(items: T[]): void {
this.setManySorted(items); this.setManySorted(items);
} }
setManySorted(items) { setManySorted(items: T[]): void {
// TODO: we can make this way faster by only looking up the first and last key, // TODO: we can make this way faster by only looking up the first and last key,
// and merging whatever is inbetween with items // and merging whatever is inbetween with items
// if items is not sorted, 💩🌀 will follow! // if items is not sorted, 💩🌀 will follow!
@ -42,11 +44,11 @@ export class SortedArray extends BaseObservableList {
} }
} }
findAndUpdate(predicate, updater) { findAndUpdate(predicate: (value: T) => boolean, updater: (value: T) => any | false): boolean {
return findAndUpdateInArray(predicate, this._items, this, updater); return findAndUpdateInArray(predicate, this._items, this, updater);
} }
getAndUpdate(item, updater, updateParams = null) { getAndUpdate(item: T, updater: (existing: T, item: T) => any, updateParams: any = null): void {
const idx = this.indexOf(item); const idx = this.indexOf(item);
if (idx !== -1) { if (idx !== -1) {
const existingItem = this._items[idx]; const existingItem = this._items[idx];
@ -56,7 +58,7 @@ export class SortedArray extends BaseObservableList {
} }
} }
update(item, updateParams = null) { update(item: T, updateParams: any = null): void {
const idx = this.indexOf(item); const idx = this.indexOf(item);
if (idx !== -1) { if (idx !== -1) {
this._items[idx] = item; this._items[idx] = item;
@ -64,7 +66,7 @@ export class SortedArray extends BaseObservableList {
} }
} }
indexOf(item) { indexOf(item: T): number {
const idx = sortedIndex(this._items, item, this._comparator); const idx = sortedIndex(this._items, item, this._comparator);
if (idx < this._items.length && this._comparator(this._items[idx], item) === 0) { if (idx < this._items.length && this._comparator(this._items[idx], item) === 0) {
return idx; return idx;
@ -73,7 +75,7 @@ export class SortedArray extends BaseObservableList {
} }
} }
_getNext(item) { _getNext(item: T): T | undefined {
let idx = sortedIndex(this._items, item, this._comparator); let idx = sortedIndex(this._items, item, this._comparator);
while(idx < this._items.length && this._comparator(this._items[idx], item) <= 0) { while(idx < this._items.length && this._comparator(this._items[idx], item) <= 0) {
idx += 1; idx += 1;
@ -81,7 +83,7 @@ export class SortedArray extends BaseObservableList {
return this.get(idx); return this.get(idx);
} }
set(item, updateParams = null) { set(item: T, updateParams: any = null): void {
const idx = sortedIndex(this._items, item, this._comparator); const idx = sortedIndex(this._items, item, this._comparator);
if (idx >= this._items.length || this._comparator(this._items[idx], item) !== 0) { if (idx >= this._items.length || this._comparator(this._items[idx], item) !== 0) {
this._items.splice(idx, 0, item); this._items.splice(idx, 0, item);
@ -92,21 +94,21 @@ export class SortedArray extends BaseObservableList {
} }
} }
get(idx) { get(idx: number): T | undefined {
return this._items[idx]; return this._items[idx];
} }
remove(idx) { remove(idx: number): void {
const item = this._items[idx]; const item = this._items[idx];
this._items.splice(idx, 1); this._items.splice(idx, 1);
this.emitRemove(idx, item); this.emitRemove(idx, item);
} }
get array() { get array(): T[] {
return this._items; return this._items;
} }
get length() { get length(): number {
return this._items.length; return this._items.length;
} }
@ -116,8 +118,11 @@ export class SortedArray extends BaseObservableList {
} }
// iterator that works even if the current value is removed while iterating // iterator that works even if the current value is removed while iterating
class Iterator { class Iterator<T> {
constructor(sortedArray) { private _sortedArray: SortedArray<T> | null
private _current: T | null | undefined
constructor(sortedArray: SortedArray<T>) {
this._sortedArray = sortedArray; this._sortedArray = sortedArray;
this._current = null; this._current = null;
} }
@ -145,7 +150,7 @@ class Iterator {
export function tests() { export function tests() {
return { return {
"setManyUnsorted": assert => { "setManyUnsorted": assert => {
const sa = new SortedArray((a, b) => a.localeCompare(b)); const sa = new SortedArray<string>((a, b) => a.localeCompare(b));
sa.setManyUnsorted(["b", "a", "c"]); sa.setManyUnsorted(["b", "a", "c"]);
assert.equal(sa.length, 3); assert.equal(sa.length, 3);
assert.equal(sa.get(0), "a"); assert.equal(sa.get(0), "a");
@ -153,7 +158,7 @@ export function tests() {
assert.equal(sa.get(2), "c"); assert.equal(sa.get(2), "c");
}, },
"_getNext": assert => { "_getNext": assert => {
const sa = new SortedArray((a, b) => a.localeCompare(b)); const sa = new SortedArray<string>((a, b) => a.localeCompare(b));
sa.setManyUnsorted(["b", "a", "f"]); sa.setManyUnsorted(["b", "a", "f"]);
assert.equal(sa._getNext("a"), "b"); assert.equal(sa._getNext("a"), "b");
assert.equal(sa._getNext("b"), "f"); assert.equal(sa._getNext("b"), "f");
@ -162,7 +167,7 @@ export function tests() {
assert.equal(sa._getNext("f"), undefined); assert.equal(sa._getNext("f"), undefined);
}, },
"iterator with removals": assert => { "iterator with removals": assert => {
const queue = new SortedArray((a, b) => a.idx - b.idx); const queue = new SortedArray<{idx: number}>((a, b) => a.idx - b.idx);
queue.setManyUnsorted([{idx: 5}, {idx: 3}, {idx: 1}, {idx: 4}, {idx: 2}]); queue.setManyUnsorted([{idx: 5}, {idx: 3}, {idx: 1}, {idx: 4}, {idx: 2}]);
const it = queue[Symbol.iterator](); const it = queue[Symbol.iterator]();
assert.equal(it.next().value.idx, 1); assert.equal(it.next().value.idx, 1);

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import {Range, RangeZone} from "./Range"; import {Range, RangeZone} from "./Range";
import {defaultObserverWith} from "../../../../observable/list/BaseObservableList";
function skipOnIterator<T>(it: Iterator<T>, pos: number): boolean { function skipOnIterator<T>(it: Iterator<T>, pos: number): boolean {
let i = 0; let i = 0;
@ -268,7 +269,7 @@ export function tests() {
const list = new ObservableArray(["b", "c", "d", "e"]); const list = new ObservableArray(["b", "c", "d", "e"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let added = false; let added = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onAdd(idx, value) { onAdd(idx, value) {
added = true; added = true;
const result = range.queryAdd(idx, value, list); const result = range.queryAdd(idx, value, list);
@ -280,7 +281,7 @@ export function tests() {
newRange: new ListRange(1, 3, 5) newRange: new ListRange(1, 3, 5)
}); });
} }
}); }));
list.insert(0, "a"); list.insert(0, "a");
assert(added); assert(added);
}, },
@ -288,7 +289,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "d", "e"]); const list = new ObservableArray(["a", "b", "d", "e"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let added = false; let added = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onAdd(idx, value) { onAdd(idx, value) {
added = true; added = true;
const result = range.queryAdd(idx, value, list); const result = range.queryAdd(idx, value, list);
@ -300,7 +301,7 @@ export function tests() {
newRange: new ListRange(1, 3, 5) newRange: new ListRange(1, 3, 5)
}); });
} }
}); }));
list.insert(2, "c"); list.insert(2, "c");
assert(added); assert(added);
}, },
@ -308,7 +309,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d"]); const list = new ObservableArray(["a", "b", "c", "d"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let added = false; let added = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onAdd(idx, value) { onAdd(idx, value) {
added = true; added = true;
const result = range.queryAdd(idx, value, list); const result = range.queryAdd(idx, value, list);
@ -317,7 +318,7 @@ export function tests() {
newRange: new ListRange(1, 3, 5) newRange: new ListRange(1, 3, 5)
}); });
} }
}); }));
list.insert(4, "e"); list.insert(4, "e");
assert(added); assert(added);
}, },
@ -326,7 +327,7 @@ export function tests() {
const viewportItemCount = 4; const viewportItemCount = 4;
const range = new ListRange(0, 3, list.length, viewportItemCount); const range = new ListRange(0, 3, list.length, viewportItemCount);
let added = false; let added = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onAdd(idx, value) { onAdd(idx, value) {
added = true; added = true;
const result = range.queryAdd(idx, value, list); const result = range.queryAdd(idx, value, list);
@ -337,7 +338,7 @@ export function tests() {
value: "c" value: "c"
}); });
} }
}); }));
list.insert(2, "c"); list.insert(2, "c");
assert(added); assert(added);
}, },
@ -345,7 +346,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let removed = false; let removed = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onRemove(idx) { onRemove(idx) {
removed = true; removed = true;
const result = range.queryRemove(idx, list); const result = range.queryRemove(idx, list);
@ -357,7 +358,7 @@ export function tests() {
newRange: new ListRange(1, 3, 4) newRange: new ListRange(1, 3, 4)
}); });
} }
}); }));
list.remove(0); list.remove(0);
assert(removed); assert(removed);
}, },
@ -365,7 +366,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let removed = false; let removed = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onRemove(idx) { onRemove(idx) {
removed = true; removed = true;
const result = range.queryRemove(idx, list); const result = range.queryRemove(idx, list);
@ -378,7 +379,7 @@ export function tests() {
}); });
assert.equal(list.length, 4); assert.equal(list.length, 4);
} }
}); }));
list.remove(2); list.remove(2);
assert(removed); assert(removed);
}, },
@ -386,7 +387,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let removed = false; let removed = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onRemove(idx) { onRemove(idx) {
removed = true; removed = true;
const result = range.queryRemove(idx, list); const result = range.queryRemove(idx, list);
@ -395,7 +396,7 @@ export function tests() {
newRange: new ListRange(1, 3, 4) newRange: new ListRange(1, 3, 4)
}); });
} }
}); }));
list.remove(3); list.remove(3);
assert(removed); assert(removed);
}, },
@ -403,7 +404,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c"]); const list = new ObservableArray(["a", "b", "c"]);
const range = new ListRange(1, 3, list.length); const range = new ListRange(1, 3, list.length);
let removed = false; let removed = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onRemove(idx) { onRemove(idx) {
removed = true; removed = true;
const result = range.queryRemove(idx, list); const result = range.queryRemove(idx, list);
@ -415,7 +416,7 @@ export function tests() {
value: "a" value: "a"
}); });
} }
}); }));
list.remove(2); list.remove(2);
assert(removed); assert(removed);
}, },
@ -423,7 +424,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c"]); const list = new ObservableArray(["a", "b", "c"]);
const range = new ListRange(0, 3, list.length); const range = new ListRange(0, 3, list.length);
let removed = false; let removed = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onRemove(idx) { onRemove(idx) {
removed = true; removed = true;
const result = range.queryRemove(idx, list); const result = range.queryRemove(idx, list);
@ -433,7 +434,7 @@ export function tests() {
removeIdx: 2, removeIdx: 2,
}); });
} }
}); }));
list.remove(2); list.remove(2);
assert(removed); assert(removed);
}, },
@ -441,7 +442,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(1, 4, list.length); const range = new ListRange(1, 4, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -451,7 +452,7 @@ export function tests() {
toIdx: 3 toIdx: 3
}); });
} }
}); }));
list.move(2, 3); list.move(2, 3);
assert(moved); assert(moved);
}, },
@ -459,7 +460,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(2, 5, list.length); const range = new ListRange(2, 5, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -470,7 +471,7 @@ export function tests() {
value: "a" value: "a"
}); });
} }
}); }));
list.move(0, 3); // move "a" to after "d" list.move(0, 3); // move "a" to after "d"
assert(moved); assert(moved);
}, },
@ -478,7 +479,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(0, 3, list.length); const range = new ListRange(0, 3, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -489,7 +490,7 @@ export function tests() {
value: "e" value: "e"
}); });
} }
}); }));
list.move(4, 1); // move "e" to before "b" list.move(4, 1); // move "e" to before "b"
assert(moved); assert(moved);
}, },
@ -497,7 +498,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(0, 3, list.length); const range = new ListRange(0, 3, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -508,7 +509,7 @@ export function tests() {
value: "d" value: "d"
}); });
} }
}); }));
list.move(1, 3); // move "b" to after "d" list.move(1, 3); // move "b" to after "d"
assert(moved); assert(moved);
}, },
@ -516,7 +517,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(2, 5, list.length); const range = new ListRange(2, 5, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -527,7 +528,7 @@ export function tests() {
value: "b" value: "b"
}); });
} }
}); }));
list.move(3, 0); // move "d" to before "a" list.move(3, 0); // move "d" to before "a"
assert(moved); assert(moved);
}, },
@ -535,7 +536,7 @@ export function tests() {
const list = new ObservableArray(["a", "b", "c", "d", "e"]); const list = new ObservableArray(["a", "b", "c", "d", "e"]);
const range = new ListRange(1, 4, list.length); const range = new ListRange(1, 4, list.length);
let moved = false; let moved = false;
list.subscribe({ list.subscribe(defaultObserverWith({
onMove(fromIdx, toIdx, value) { onMove(fromIdx, toIdx, value) {
moved = true; moved = true;
const result = range.queryMove(fromIdx, toIdx, value, list); const result = range.queryMove(fromIdx, toIdx, value, list);
@ -546,7 +547,7 @@ export function tests() {
value: "e" value: "e"
}); });
} }
}); }));
list.move(0, 4); // move "a" to after "e" list.move(0, 4); // move "a" to after "e"
assert(moved); assert(moved);
}, },

View file

@ -43,7 +43,7 @@ export class Range {
return range.start < this.end && this.start < range.end; return range.start < this.end && this.start < range.end;
} }
forEachInIterator<T>(it: IterableIterator<T>, callback: ((T, i: number) => void)) { forEachInIterator<T>(it: Iterator<T>, callback: ((T, i: number) => void)) {
let i = 0; let i = 0;
for (i = 0; i < this.start; i += 1) { for (i = 0; i < this.start; i += 1) {
it.next(); it.next();