Merge pull request #606 from vector-im/bwindels/typescript-observable-2
Typescript conversion of yet more observables
This commit is contained in:
commit
08314bd4b5
13 changed files with 181 additions and 168 deletions
|
@ -188,7 +188,7 @@ import {NullLogItem, NullLogger} from "../../../../logging/NullLogger";
|
|||
import {HomeServer as MockHomeServer} from "../../../../mocks/HomeServer.js";
|
||||
// other imports
|
||||
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 {PowerLevels} from "../../../../matrix/room/PowerLevels.js";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
export function tests() {
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {SortedArray} from "../../../observable/list/SortedArray.js";
|
||||
import {SortedArray} from "../../../observable/list/SortedArray";
|
||||
import {ConnectionError} from "../../error.js";
|
||||
import {PendingEvent, SendStatus} from "./PendingEvent.js";
|
||||
import {makeTxnId, isTxnId} from "../../common.js";
|
||||
|
|
|
@ -20,11 +20,11 @@ import {MappedMap} from "./map/MappedMap.js";
|
|||
import {JoinedMap} from "./map/JoinedMap.js";
|
||||
import {BaseObservableMap} from "./map/BaseObservableMap.js";
|
||||
// re-export "root" (of chain) collections
|
||||
export { ObservableArray } from "./list/ObservableArray.js";
|
||||
export { SortedArray } from "./list/SortedArray.js";
|
||||
export { MappedList } from "./list/MappedList.js";
|
||||
export { AsyncMappedList } from "./list/AsyncMappedList.js";
|
||||
export { ConcatList } from "./list/ConcatList.js";
|
||||
export { ObservableArray } from "./list/ObservableArray";
|
||||
export { SortedArray } from "./list/SortedArray";
|
||||
export { MappedList } from "./list/MappedList";
|
||||
export { AsyncMappedList } from "./list/AsyncMappedList";
|
||||
export { ConcatList } from "./list/ConcatList";
|
||||
export { ObservableMap } from "./map/ObservableMap.js";
|
||||
|
||||
// avoid circular dependency between these classes
|
||||
|
|
|
@ -15,15 +15,14 @@ See the License for the specific language governing permissions and
|
|||
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 {
|
||||
constructor(sourceList, mapper, updater, removeCallback) {
|
||||
super(sourceList, mapper, updater, removeCallback);
|
||||
this._eventQueue = null;
|
||||
}
|
||||
export class AsyncMappedList<F,T> extends BaseMappedList<F,T,Promise<T>> implements IListObserver<F> {
|
||||
private _eventQueue: AsyncEvent<F>[] | null = null;
|
||||
private _flushing: boolean = false;
|
||||
|
||||
onSubscribeFirst() {
|
||||
onSubscribeFirst(): void {
|
||||
this._sourceUnsubscribe = this._sourceList.subscribe(this);
|
||||
this._eventQueue = [];
|
||||
this._mappedValues = [];
|
||||
|
@ -35,122 +34,112 @@ export class AsyncMappedList extends BaseMappedList {
|
|||
this._flush();
|
||||
}
|
||||
|
||||
async _flush() {
|
||||
async _flush(): Promise<void> {
|
||||
if (this._flushing) {
|
||||
return;
|
||||
}
|
||||
this._flushing = true;
|
||||
try {
|
||||
while (this._eventQueue.length) {
|
||||
const event = this._eventQueue.shift();
|
||||
await event.run(this);
|
||||
while (this._eventQueue!.length) {
|
||||
const event = this._eventQueue!.shift();
|
||||
await event!.run(this);
|
||||
}
|
||||
} finally {
|
||||
this._flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
onReset() {
|
||||
onReset(): void {
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(new ResetEvent());
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
onAdd(index, value) {
|
||||
onAdd(index: number, value: F): void {
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(new AddEvent(index, value));
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(index, value, params) {
|
||||
onUpdate(index: number, value: F, params: any): void {
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(new UpdateEvent(index, value, params));
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
onRemove(index) {
|
||||
onRemove(index: number): void {
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(new RemoveEvent(index));
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
onMove(fromIdx, toIdx) {
|
||||
onMove(fromIdx: number, toIdx: number): void {
|
||||
if (this._eventQueue) {
|
||||
this._eventQueue.push(new MoveEvent(fromIdx, toIdx));
|
||||
this._flush();
|
||||
}
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
this._sourceUnsubscribe();
|
||||
onUnsubscribeLast(): void {
|
||||
this._sourceUnsubscribe!();
|
||||
this._eventQueue = null;
|
||||
this._mappedValues = null;
|
||||
}
|
||||
}
|
||||
|
||||
class AddEvent {
|
||||
constructor(index, value) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
}
|
||||
type AsyncEvent<F> = AddEvent<F> | UpdateEvent<F> | RemoveEvent<F> | MoveEvent<F> | ResetEvent<F>
|
||||
|
||||
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);
|
||||
runAdd(list, this.index, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
class UpdateEvent {
|
||||
constructor(index, value, params) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.params = params;
|
||||
}
|
||||
class UpdateEvent<F> {
|
||||
constructor(public index: number, public value: F, public params: any) {}
|
||||
|
||||
async run(list) {
|
||||
async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
|
||||
runUpdate(list, this.index, this.value, this.params);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveEvent {
|
||||
constructor(index) {
|
||||
this.index = index;
|
||||
}
|
||||
class RemoveEvent<F> {
|
||||
constructor(public index: number) {}
|
||||
|
||||
async run(list) {
|
||||
async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
|
||||
runRemove(list, this.index);
|
||||
}
|
||||
}
|
||||
|
||||
class MoveEvent {
|
||||
constructor(fromIdx, toIdx) {
|
||||
this.fromIdx = fromIdx;
|
||||
this.toIdx = toIdx;
|
||||
}
|
||||
class MoveEvent<F> {
|
||||
constructor(public fromIdx: number, public toIdx: number) {}
|
||||
|
||||
async run(list) {
|
||||
async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
|
||||
runMove(list, this.fromIdx, this.toIdx);
|
||||
}
|
||||
}
|
||||
|
||||
class ResetEvent {
|
||||
async run(list) {
|
||||
class ResetEvent<F> {
|
||||
async run<T>(list: AsyncMappedList<F,T>): Promise<void> {
|
||||
runReset(list);
|
||||
}
|
||||
}
|
||||
|
||||
import {ObservableArray} from "./ObservableArray.js";
|
||||
import {ObservableArray} from "./ObservableArray";
|
||||
import {ListObserver} from "../../mocks/ListObserver.js";
|
||||
|
||||
export function tests() {
|
||||
return {
|
||||
"events are emitted in order": async assert => {
|
||||
const double = n => n * n;
|
||||
const source = new ObservableArray();
|
||||
const source = new ObservableArray<number>();
|
||||
const mapper = new AsyncMappedList(source, async n => {
|
||||
await new Promise(r => setTimeout(r, n));
|
||||
return {n: double(n)};
|
|
@ -21,15 +21,15 @@ import {findAndUpdateInArray} from "./common";
|
|||
export type Mapper<F,T> = (value: F) => T
|
||||
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 _sourceUnsubscribe: (() => void) | null = null;
|
||||
_mapper: Mapper<F,T>;
|
||||
_updater: Updater<F,T>;
|
||||
_mapper: Mapper<F,R>;
|
||||
_updater?: Updater<F,T>;
|
||||
_removeCallback?: (value: T) => void;
|
||||
_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();
|
||||
this._sourceList = sourceList;
|
||||
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.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];
|
||||
if (list._updater) {
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
list._mappedValues!.splice(index, 1);
|
||||
if (list._removeCallback) {
|
||||
|
@ -72,14 +72,14 @@ export function runRemove<F,T>(list: BaseMappedList<F,T>, index: number): void {
|
|||
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];
|
||||
list._mappedValues!.splice(fromIdx, 1);
|
||||
list._mappedValues!.splice(toIdx, 0, 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.emitReset();
|
||||
}
|
||||
|
|
|
@ -24,7 +24,18 @@ export interface IListObserver<T> {
|
|||
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() {
|
||||
for(let h of this._handlers) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -14,16 +14,18 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {BaseObservableList} from "./BaseObservableList";
|
||||
import {BaseObservableList, IListObserver} from "./BaseObservableList";
|
||||
|
||||
export class ConcatList extends BaseObservableList {
|
||||
constructor(...sourceLists) {
|
||||
export class ConcatList<T> extends BaseObservableList<T> implements IListObserver<T> {
|
||||
protected _sourceLists: BaseObservableList<T>[];
|
||||
protected _sourceUnsubscribes: (() => void)[] | null = null;
|
||||
|
||||
constructor(...sourceLists: BaseObservableList<T>[]) {
|
||||
super();
|
||||
this._sourceLists = sourceLists;
|
||||
this._sourceUnsubscribes = null;
|
||||
}
|
||||
|
||||
_offsetForSource(sourceList) {
|
||||
_offsetForSource(sourceList: BaseObservableList<T>): number {
|
||||
const listIdx = this._sourceLists.indexOf(sourceList);
|
||||
let offset = 0;
|
||||
for (let i = 0; i < listIdx; ++i) {
|
||||
|
@ -32,17 +34,17 @@ export class ConcatList extends BaseObservableList {
|
|||
return offset;
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
onSubscribeFirst(): void {
|
||||
this._sourceUnsubscribes = this._sourceLists.map(sourceList => sourceList.subscribe(this));
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
for (const sourceUnsubscribe of this._sourceUnsubscribes) {
|
||||
onUnsubscribeLast(): void {
|
||||
for (const sourceUnsubscribe of this._sourceUnsubscribes!) {
|
||||
sourceUnsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
onReset() {
|
||||
onReset(): void {
|
||||
// TODO: not ideal if other source lists are large
|
||||
// but working impl for now
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
// as we are not supposed to call `length` on any uninitialized list
|
||||
if (!this._sourceUnsubscribes) {
|
||||
|
@ -67,16 +69,16 @@ export class ConcatList extends BaseObservableList {
|
|||
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);
|
||||
}
|
||||
|
||||
onMove(fromIdx, toIdx, value, sourceList) {
|
||||
onMove(fromIdx: number, toIdx: number, value: T, sourceList: BaseObservableList<T>): void {
|
||||
const offset = this._offsetForSource(sourceList);
|
||||
this.emitMove(offset + fromIdx, offset + toIdx, value);
|
||||
}
|
||||
|
||||
get length() {
|
||||
get length(): number {
|
||||
let len = 0;
|
||||
for (let i = 0; i < this._sourceLists.length; ++i) {
|
||||
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() {
|
||||
return {
|
||||
test_length(assert) {
|
||||
|
@ -133,13 +136,13 @@ export async function tests() {
|
|||
const list2 = new ObservableArray([11, 12, 13]);
|
||||
const all = new ConcatList(list1, list2);
|
||||
let fired = false;
|
||||
all.subscribe({
|
||||
all.subscribe(defaultObserverWith({
|
||||
onAdd(index, value) {
|
||||
fired = true;
|
||||
assert.equal(index, 4);
|
||||
assert.equal(value, 11.5);
|
||||
}
|
||||
});
|
||||
}));
|
||||
list2.insert(1, 11.5);
|
||||
assert(fired);
|
||||
},
|
||||
|
@ -148,13 +151,13 @@ export async function tests() {
|
|||
const list2 = new ObservableArray([11, 12, 13]);
|
||||
const all = new ConcatList(list1, list2);
|
||||
let fired = false;
|
||||
all.subscribe({
|
||||
all.subscribe(defaultObserverWith({
|
||||
onUpdate(index, value) {
|
||||
fired = true;
|
||||
assert.equal(index, 4);
|
||||
assert.equal(value, 10);
|
||||
}
|
||||
});
|
||||
}));
|
||||
list2.emitUpdate(1, 10);
|
||||
assert(fired);
|
||||
},
|
|
@ -15,9 +15,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {IListObserver} from "./BaseObservableList";
|
||||
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() {
|
||||
this._sourceUnsubscribe = this._sourceList.subscribe(this);
|
||||
this._mappedValues = [];
|
||||
|
@ -26,16 +27,16 @@ export class MappedList extends BaseMappedList {
|
|||
}
|
||||
}
|
||||
|
||||
onReset() {
|
||||
onReset(): void {
|
||||
runReset(this);
|
||||
}
|
||||
|
||||
onAdd(index, value) {
|
||||
onAdd(index: number, value: F): void {
|
||||
const mappedValue = this._mapper(value);
|
||||
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 (!this._mappedValues) {
|
||||
return;
|
||||
|
@ -43,24 +44,25 @@ export class MappedList extends BaseMappedList {
|
|||
runUpdate(this, index, value, params);
|
||||
}
|
||||
|
||||
onRemove(index) {
|
||||
onRemove(index: number): void {
|
||||
runRemove(this, index);
|
||||
}
|
||||
|
||||
onMove(fromIdx, toIdx) {
|
||||
onMove(fromIdx: number, toIdx: number): void {
|
||||
runMove(this, fromIdx, toIdx);
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
this._sourceUnsubscribe();
|
||||
onUnsubscribeLast(): void {
|
||||
this._sourceUnsubscribe!();
|
||||
}
|
||||
}
|
||||
|
||||
import {ObservableArray} from "./ObservableArray.js";
|
||||
import {ObservableArray} from "./ObservableArray";
|
||||
import {BaseObservableList} from "./BaseObservableList";
|
||||
import {defaultObserverWith} from "./BaseObservableList";
|
||||
|
||||
export async function tests() {
|
||||
class MockList extends BaseObservableList {
|
||||
class MockList extends BaseObservableList<number> {
|
||||
get length() {
|
||||
return 0;
|
||||
}
|
||||
|
@ -74,26 +76,26 @@ export async function tests() {
|
|||
const source = new MockList();
|
||||
const mapped = new MappedList(source, n => {return {n: n*n};});
|
||||
let fired = false;
|
||||
const unsubscribe = mapped.subscribe({
|
||||
const unsubscribe = mapped.subscribe(defaultObserverWith({
|
||||
onAdd(idx, value) {
|
||||
fired = true;
|
||||
assert.equal(idx, 0);
|
||||
assert.equal(value.n, 36);
|
||||
}
|
||||
});
|
||||
}));
|
||||
source.emitAdd(0, 6);
|
||||
assert(fired);
|
||||
unsubscribe();
|
||||
},
|
||||
test_update(assert) {
|
||||
const source = new MockList();
|
||||
const mapped = new MappedList(
|
||||
const mapped = new MappedList<number, { n: number, m?: number }>(
|
||||
source,
|
||||
n => {return {n: n*n};},
|
||||
(o, p, n) => o.m = n*n
|
||||
);
|
||||
let fired = false;
|
||||
const unsubscribe = mapped.subscribe({
|
||||
const unsubscribe = mapped.subscribe(defaultObserverWith({
|
||||
onAdd() {},
|
||||
onUpdate(idx, value) {
|
||||
fired = true;
|
||||
|
@ -101,7 +103,7 @@ export async function tests() {
|
|||
assert.equal(value.n, 36);
|
||||
assert.equal(value.m, 49);
|
||||
}
|
||||
});
|
||||
}));
|
||||
source.emitAdd(0, 6);
|
||||
source.emitUpdate(0, 7);
|
||||
assert(fired);
|
||||
|
@ -113,9 +115,9 @@ export async function tests() {
|
|||
source,
|
||||
n => {return n*n;}
|
||||
);
|
||||
mapped.subscribe({
|
||||
mapped.subscribe(defaultObserverWith({
|
||||
onUpdate() { assert.fail(); }
|
||||
});
|
||||
}));
|
||||
assert.equal(mapped.findAndUpdate(
|
||||
n => n === 100,
|
||||
() => assert.fail()
|
||||
|
@ -127,9 +129,9 @@ export async function tests() {
|
|||
source,
|
||||
n => {return n*n;}
|
||||
);
|
||||
mapped.subscribe({
|
||||
mapped.subscribe(defaultObserverWith({
|
||||
onUpdate() { assert.fail(); }
|
||||
});
|
||||
}));
|
||||
let fired = false;
|
||||
assert.equal(mapped.findAndUpdate(
|
||||
n => n === 9,
|
||||
|
@ -148,14 +150,14 @@ export async function tests() {
|
|||
n => {return n*n;}
|
||||
);
|
||||
let fired = false;
|
||||
mapped.subscribe({
|
||||
mapped.subscribe(defaultObserverWith({
|
||||
onUpdate(idx, n, params) {
|
||||
assert.equal(idx, 1);
|
||||
assert.equal(n, 9);
|
||||
assert.equal(params, "param");
|
||||
fired = true;
|
||||
}
|
||||
});
|
||||
}));
|
||||
assert.equal(mapped.findAndUpdate(n => n === 9, () => "param"), true);
|
||||
assert.equal(fired, true);
|
||||
},
|
|
@ -16,35 +16,37 @@ limitations under the License.
|
|||
|
||||
import {BaseObservableList} from "./BaseObservableList";
|
||||
|
||||
export class ObservableArray extends BaseObservableList {
|
||||
constructor(initialValues = []) {
|
||||
export class ObservableArray<T> extends BaseObservableList<T> {
|
||||
private _items: T[];
|
||||
|
||||
constructor(initialValues: T[] = []) {
|
||||
super();
|
||||
this._items = initialValues;
|
||||
}
|
||||
|
||||
append(item) {
|
||||
append(item: T): void {
|
||||
this._items.push(item);
|
||||
this.emitAdd(this._items.length - 1, item);
|
||||
}
|
||||
|
||||
remove(idx) {
|
||||
remove(idx: number): void {
|
||||
const [item] = this._items.splice(idx, 1);
|
||||
this.emitRemove(idx, item);
|
||||
}
|
||||
|
||||
insertMany(idx, items) {
|
||||
insertMany(idx: number, items: T[]): void {
|
||||
for(let item of items) {
|
||||
this.insert(idx, item);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
insert(idx, item) {
|
||||
insert(idx: number, item: T): void {
|
||||
this._items.splice(idx, 0, item);
|
||||
this.emitAdd(idx, item);
|
||||
}
|
||||
|
||||
move(fromIdx, toIdx) {
|
||||
move(fromIdx: number, toIdx: number): void {
|
||||
if (fromIdx < this._items.length && toIdx < this._items.length) {
|
||||
const [item] = this._items.splice(fromIdx, 1);
|
||||
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) {
|
||||
this._items[idx] = item;
|
||||
this.emitUpdate(idx, item, params);
|
||||
}
|
||||
}
|
||||
|
||||
get array() {
|
||||
get array(): Readonly<T[]> {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
at(idx) {
|
||||
at(idx: number): T | undefined {
|
||||
if (this._items && idx >= 0 && idx < this._items.length) {
|
||||
return this._items[idx];
|
||||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
get length(): number {
|
||||
return this._items.length;
|
||||
}
|
||||
|
|
@ -18,18 +18,20 @@ import {BaseObservableList} from "./BaseObservableList";
|
|||
import {sortedIndex} from "../../utils/sortedIndex";
|
||||
import {findAndUpdateInArray} from "./common";
|
||||
|
||||
export class SortedArray extends BaseObservableList {
|
||||
constructor(comparator) {
|
||||
export class SortedArray<T> extends BaseObservableList<T> {
|
||||
private _comparator: (left: T, right: T) => number;
|
||||
private _items: T[] = [];
|
||||
|
||||
constructor(comparator: (left: T, right: T) => number) {
|
||||
super();
|
||||
this._comparator = comparator;
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
setManyUnsorted(items) {
|
||||
setManyUnsorted(items: T[]): void {
|
||||
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,
|
||||
// and merging whatever is inbetween with items
|
||||
// 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);
|
||||
}
|
||||
|
||||
getAndUpdate(item, updater, updateParams = null) {
|
||||
getAndUpdate(item: T, updater: (existing: T, item: T) => any, updateParams: any = null): void {
|
||||
const idx = this.indexOf(item);
|
||||
if (idx !== -1) {
|
||||
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);
|
||||
if (idx !== -1) {
|
||||
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);
|
||||
if (idx < this._items.length && this._comparator(this._items[idx], item) === 0) {
|
||||
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);
|
||||
while(idx < this._items.length && this._comparator(this._items[idx], item) <= 0) {
|
||||
idx += 1;
|
||||
|
@ -81,7 +83,7 @@ export class SortedArray extends BaseObservableList {
|
|||
return this.get(idx);
|
||||
}
|
||||
|
||||
set(item, updateParams = null) {
|
||||
set(item: T, updateParams: any = null): void {
|
||||
const idx = sortedIndex(this._items, item, this._comparator);
|
||||
if (idx >= this._items.length || this._comparator(this._items[idx], item) !== 0) {
|
||||
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];
|
||||
}
|
||||
|
||||
remove(idx) {
|
||||
remove(idx: number): void {
|
||||
const item = this._items[idx];
|
||||
this._items.splice(idx, 1);
|
||||
this.emitRemove(idx, item);
|
||||
}
|
||||
|
||||
get array() {
|
||||
get array(): T[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
get length() {
|
||||
get length(): number {
|
||||
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
|
||||
class Iterator {
|
||||
constructor(sortedArray) {
|
||||
class Iterator<T> {
|
||||
private _sortedArray: SortedArray<T> | null
|
||||
private _current: T | null | undefined
|
||||
|
||||
constructor(sortedArray: SortedArray<T>) {
|
||||
this._sortedArray = sortedArray;
|
||||
this._current = null;
|
||||
}
|
||||
|
@ -145,7 +150,7 @@ class Iterator {
|
|||
export function tests() {
|
||||
return {
|
||||
"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"]);
|
||||
assert.equal(sa.length, 3);
|
||||
assert.equal(sa.get(0), "a");
|
||||
|
@ -153,7 +158,7 @@ export function tests() {
|
|||
assert.equal(sa.get(2), "c");
|
||||
},
|
||||
"_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"]);
|
||||
assert.equal(sa._getNext("a"), "b");
|
||||
assert.equal(sa._getNext("b"), "f");
|
||||
|
@ -162,7 +167,7 @@ export function tests() {
|
|||
assert.equal(sa._getNext("f"), undefined);
|
||||
},
|
||||
"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}]);
|
||||
const it = queue[Symbol.iterator]();
|
||||
assert.equal(it.next().value.idx, 1);
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {Range, RangeZone} from "./Range";
|
||||
import {defaultObserverWith} from "../../../../observable/list/BaseObservableList";
|
||||
|
||||
function skipOnIterator<T>(it: Iterator<T>, pos: number): boolean {
|
||||
let i = 0;
|
||||
|
@ -268,7 +269,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let added = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onAdd(idx, value) {
|
||||
added = true;
|
||||
const result = range.queryAdd(idx, value, list);
|
||||
|
@ -280,7 +281,7 @@ export function tests() {
|
|||
newRange: new ListRange(1, 3, 5)
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.insert(0, "a");
|
||||
assert(added);
|
||||
},
|
||||
|
@ -288,7 +289,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "d", "e"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let added = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onAdd(idx, value) {
|
||||
added = true;
|
||||
const result = range.queryAdd(idx, value, list);
|
||||
|
@ -300,7 +301,7 @@ export function tests() {
|
|||
newRange: new ListRange(1, 3, 5)
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.insert(2, "c");
|
||||
assert(added);
|
||||
},
|
||||
|
@ -308,7 +309,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let added = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onAdd(idx, value) {
|
||||
added = true;
|
||||
const result = range.queryAdd(idx, value, list);
|
||||
|
@ -317,7 +318,7 @@ export function tests() {
|
|||
newRange: new ListRange(1, 3, 5)
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.insert(4, "e");
|
||||
assert(added);
|
||||
},
|
||||
|
@ -326,7 +327,7 @@ export function tests() {
|
|||
const viewportItemCount = 4;
|
||||
const range = new ListRange(0, 3, list.length, viewportItemCount);
|
||||
let added = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onAdd(idx, value) {
|
||||
added = true;
|
||||
const result = range.queryAdd(idx, value, list);
|
||||
|
@ -337,7 +338,7 @@ export function tests() {
|
|||
value: "c"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.insert(2, "c");
|
||||
assert(added);
|
||||
},
|
||||
|
@ -345,7 +346,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let removed = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onRemove(idx) {
|
||||
removed = true;
|
||||
const result = range.queryRemove(idx, list);
|
||||
|
@ -357,7 +358,7 @@ export function tests() {
|
|||
newRange: new ListRange(1, 3, 4)
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.remove(0);
|
||||
assert(removed);
|
||||
},
|
||||
|
@ -365,7 +366,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let removed = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onRemove(idx) {
|
||||
removed = true;
|
||||
const result = range.queryRemove(idx, list);
|
||||
|
@ -378,7 +379,7 @@ export function tests() {
|
|||
});
|
||||
assert.equal(list.length, 4);
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.remove(2);
|
||||
assert(removed);
|
||||
},
|
||||
|
@ -386,7 +387,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let removed = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onRemove(idx) {
|
||||
removed = true;
|
||||
const result = range.queryRemove(idx, list);
|
||||
|
@ -395,7 +396,7 @@ export function tests() {
|
|||
newRange: new ListRange(1, 3, 4)
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.remove(3);
|
||||
assert(removed);
|
||||
},
|
||||
|
@ -403,7 +404,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c"]);
|
||||
const range = new ListRange(1, 3, list.length);
|
||||
let removed = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onRemove(idx) {
|
||||
removed = true;
|
||||
const result = range.queryRemove(idx, list);
|
||||
|
@ -415,7 +416,7 @@ export function tests() {
|
|||
value: "a"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.remove(2);
|
||||
assert(removed);
|
||||
},
|
||||
|
@ -423,7 +424,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c"]);
|
||||
const range = new ListRange(0, 3, list.length);
|
||||
let removed = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onRemove(idx) {
|
||||
removed = true;
|
||||
const result = range.queryRemove(idx, list);
|
||||
|
@ -433,7 +434,7 @@ export function tests() {
|
|||
removeIdx: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.remove(2);
|
||||
assert(removed);
|
||||
},
|
||||
|
@ -441,7 +442,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 4, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -451,7 +452,7 @@ export function tests() {
|
|||
toIdx: 3
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(2, 3);
|
||||
assert(moved);
|
||||
},
|
||||
|
@ -459,7 +460,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(2, 5, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -470,7 +471,7 @@ export function tests() {
|
|||
value: "a"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(0, 3); // move "a" to after "d"
|
||||
assert(moved);
|
||||
},
|
||||
|
@ -478,7 +479,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(0, 3, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -489,7 +490,7 @@ export function tests() {
|
|||
value: "e"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(4, 1); // move "e" to before "b"
|
||||
assert(moved);
|
||||
},
|
||||
|
@ -497,7 +498,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(0, 3, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -508,7 +509,7 @@ export function tests() {
|
|||
value: "d"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(1, 3); // move "b" to after "d"
|
||||
assert(moved);
|
||||
},
|
||||
|
@ -516,7 +517,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(2, 5, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -527,7 +528,7 @@ export function tests() {
|
|||
value: "b"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(3, 0); // move "d" to before "a"
|
||||
assert(moved);
|
||||
},
|
||||
|
@ -535,7 +536,7 @@ export function tests() {
|
|||
const list = new ObservableArray(["a", "b", "c", "d", "e"]);
|
||||
const range = new ListRange(1, 4, list.length);
|
||||
let moved = false;
|
||||
list.subscribe({
|
||||
list.subscribe(defaultObserverWith({
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
moved = true;
|
||||
const result = range.queryMove(fromIdx, toIdx, value, list);
|
||||
|
@ -546,7 +547,7 @@ export function tests() {
|
|||
value: "e"
|
||||
});
|
||||
}
|
||||
});
|
||||
}));
|
||||
list.move(0, 4); // move "a" to after "e"
|
||||
assert(moved);
|
||||
},
|
||||
|
|
|
@ -43,7 +43,7 @@ export class Range {
|
|||
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;
|
||||
for (i = 0; i < this.start; i += 1) {
|
||||
it.next();
|
||||
|
|
Reference in a new issue