Merge pull request #543 from vector-im/bwindels/typescript-observable
Typescript conversion of base observables
This commit is contained in:
commit
de22a0790f
30 changed files with 259 additions and 170 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue.js";
|
import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
export class Navigation {
|
export class Navigation {
|
||||||
constructor(allowsChild) {
|
constructor(allowsChild) {
|
||||||
|
|
|
@ -186,7 +186,7 @@ export class RoomGridViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
import {createNavigation} from "../navigation/index.js";
|
import {createNavigation} from "../navigation/index.js";
|
||||||
import {ObservableValue} from "../../observable/ObservableValue.js";
|
import {ObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
export function tests() {
|
export function tests() {
|
||||||
class RoomVMMock {
|
class RoomVMMock {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ObservableValue} from "../../observable/ObservableValue.js";
|
import {ObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Depending on the status of a room (invited, joined, archived, or none),
|
Depending on the status of a room (invited, joined, archived, or none),
|
||||||
|
@ -77,4 +77,4 @@ export class RoomViewModelObservable extends ObservableValue {
|
||||||
this.unsubscribeAll();
|
this.unsubscribeAll();
|
||||||
this.get()?.dispose();
|
this.get()?.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,7 +189,7 @@ 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.js";
|
||||||
import {ObservableValue} from "../../../../observable/ObservableValue.js";
|
import {ObservableValue} from "../../../../observable/ObservableValue";
|
||||||
import {PowerLevels} from "../../../../matrix/room/PowerLevels.js";
|
import {PowerLevels} from "../../../../matrix/room/PowerLevels.js";
|
||||||
|
|
||||||
export function tests() {
|
export function tests() {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableList} from "../../../../observable/list/BaseObservableList.js";
|
import {BaseObservableList} from "../../../../observable/list/BaseObservableList";
|
||||||
import {sortedIndex} from "../../../../utils/sortedIndex.js";
|
import {sortedIndex} from "../../../../utils/sortedIndex.js";
|
||||||
|
|
||||||
// maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary
|
// maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
writeKey as ssssWriteKey,
|
writeKey as ssssWriteKey,
|
||||||
} from "./ssss/index.js";
|
} from "./ssss/index.js";
|
||||||
import {SecretStorage} from "./ssss/SecretStorage.js";
|
import {SecretStorage} from "./ssss/SecretStorage.js";
|
||||||
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
|
||||||
|
|
||||||
const PICKLE_KEY = "DEFAULT_KEY";
|
const PICKLE_KEY = "DEFAULT_KEY";
|
||||||
const PUSHER_KEY = "pusher";
|
const PUSHER_KEY = "pusher";
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import {createEnum} from "../utils/enum.js";
|
import {createEnum} from "../utils/enum.js";
|
||||||
import {lookupHomeserver} from "./well-known.js";
|
import {lookupHomeserver} from "./well-known.js";
|
||||||
import {AbortableOperation} from "../utils/AbortableOperation";
|
import {AbortableOperation} from "../utils/AbortableOperation";
|
||||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue} from "../observable/ObservableValue";
|
||||||
import {HomeServerApi} from "./net/HomeServerApi.js";
|
import {HomeServerApi} from "./net/HomeServerApi.js";
|
||||||
import {Reconnector, ConnectionStatus} from "./net/Reconnector.js";
|
import {Reconnector, ConnectionStatus} from "./net/Reconnector.js";
|
||||||
import {ExponentialRetryDelay} from "./net/ExponentialRetryDelay.js";
|
import {ExponentialRetryDelay} from "./net/ExponentialRetryDelay.js";
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue} from "../observable/ObservableValue";
|
||||||
import {createEnum} from "../utils/enum.js";
|
import {createEnum} from "../utils/enum.js";
|
||||||
|
|
||||||
const INCREMENTAL_TIMEOUT = 30000;
|
const INCREMENTAL_TIMEOUT = 30000;
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {createEnum} from "../../utils/enum.js";
|
import {createEnum} from "../../utils/enum.js";
|
||||||
import {ObservableValue} from "../../observable/ObservableValue.js";
|
import {ObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
export const ConnectionStatus = createEnum(
|
export const ConnectionStatus = createEnum(
|
||||||
"Waiting",
|
"Waiting",
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {ObservedEventMap} from "./ObservedEventMap.js";
|
||||||
import {DecryptionSource} from "../e2ee/common.js";
|
import {DecryptionSource} from "../e2ee/common.js";
|
||||||
import {ensureLogItem} from "../../logging/utils.js";
|
import {ensureLogItem} from "../../logging/utils.js";
|
||||||
import {PowerLevels} from "./PowerLevels.js";
|
import {PowerLevels} from "./PowerLevels.js";
|
||||||
import {RetainedObservableValue} from "../../observable/ObservableValue.js";
|
import {RetainedObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
|
const EVENT_ENCRYPTED_TYPE = "m.room.encrypted";
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableValue} from "../../observable/ObservableValue.js";
|
import {BaseObservableValue} from "../../observable/ObservableValue";
|
||||||
|
|
||||||
export class ObservedEventMap {
|
export class ObservedEventMap {
|
||||||
constructor(notifyEmpty) {
|
constructor(notifyEmpty) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ObservableValue} from "../observable/ObservableValue.js";
|
import {ObservableValue} from "../observable/ObservableValue";
|
||||||
|
|
||||||
class Timeout {
|
class Timeout {
|
||||||
constructor(elapsed, ms) {
|
constructor(elapsed, ms) {
|
||||||
|
|
|
@ -14,20 +14,22 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class BaseObservable {
|
// we return undefined so you can reassign any member
|
||||||
constructor() {
|
// that uses `member?: T` syntax in one statement.
|
||||||
this._handlers = new Set();
|
export type SubscriptionHandle = () => undefined;
|
||||||
}
|
|
||||||
|
|
||||||
onSubscribeFirst() {
|
export abstract class BaseObservable<T> {
|
||||||
|
protected _handlers: Set<T> = new Set<T>();
|
||||||
|
|
||||||
|
onSubscribeFirst(): void {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnsubscribeLast() {
|
onUnsubscribeLast(): void {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(handler) {
|
subscribe(handler: T): SubscriptionHandle {
|
||||||
this._handlers.add(handler);
|
this._handlers.add(handler);
|
||||||
if (this._handlers.size === 1) {
|
if (this._handlers.size === 1) {
|
||||||
this.onSubscribeFirst();
|
this.onSubscribeFirst();
|
||||||
|
@ -37,25 +39,24 @@ export class BaseObservable {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe(handler) {
|
unsubscribe(handler?: T): undefined {
|
||||||
if (handler) {
|
if (handler) {
|
||||||
this._handlers.delete(handler);
|
this._handlers.delete(handler);
|
||||||
if (this._handlers.size === 0) {
|
if (this._handlers.size === 0) {
|
||||||
this.onUnsubscribeLast();
|
this.onUnsubscribeLast();
|
||||||
}
|
}
|
||||||
handler = null;
|
|
||||||
}
|
}
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribeAll() {
|
unsubscribeAll(): void {
|
||||||
if (this._handlers.size !== 0) {
|
if (this._handlers.size !== 0) {
|
||||||
this._handlers.clear();
|
this._handlers.clear();
|
||||||
this.onUnsubscribeLast();
|
this.onUnsubscribeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasSubscriptions() {
|
get hasSubscriptions(): boolean {
|
||||||
return this._handlers.size !== 0;
|
return this._handlers.size !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,13 +64,11 @@ export class BaseObservable {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tests() {
|
export function tests() {
|
||||||
class Collection extends BaseObservable {
|
class Collection extends BaseObservable<{}> {
|
||||||
constructor() {
|
firstSubscribeCalls: number = 0;
|
||||||
super();
|
firstUnsubscribeCalls: number = 0;
|
||||||
this.firstSubscribeCalls = 0;
|
|
||||||
this.firstUnsubscribeCalls = 0;
|
onSubscribeFirst() { this.firstSubscribeCalls += 1; }
|
||||||
}
|
|
||||||
onSubscribeFirst() { this.firstSubscribeCalls += 1; }
|
|
||||||
onUnsubscribeLast() { this.firstUnsubscribeCalls += 1; }
|
onUnsubscribeLast() { this.firstUnsubscribeCalls += 1; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,21 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {AbortError} from "../utils/error.js";
|
import {AbortError} from "../utils/error.js";
|
||||||
import {BaseObservable} from "./BaseObservable.js";
|
import {BaseObservable} from "./BaseObservable";
|
||||||
|
|
||||||
// like an EventEmitter, but doesn't have an event type
|
// like an EventEmitter, but doesn't have an event type
|
||||||
export class BaseObservableValue extends BaseObservable {
|
export abstract class BaseObservableValue<T> extends BaseObservable<(value: T) => void> {
|
||||||
emit(argument) {
|
emit(argument: T) {
|
||||||
for (const h of this._handlers) {
|
for (const h of this._handlers) {
|
||||||
h(argument);
|
h(argument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
abstract get(): T;
|
||||||
throw new Error("unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
waitFor(predicate) {
|
waitFor(predicate: (value: T) => boolean): IWaitHandle<T> {
|
||||||
if (predicate(this.get())) {
|
if (predicate(this.get())) {
|
||||||
return new ResolvedWaitForHandle(Promise.resolve(this.get()));
|
return new ResolvedWaitForHandle(Promise.resolve(this.get()));
|
||||||
} else {
|
} else {
|
||||||
|
@ -38,8 +36,17 @@ export class BaseObservableValue extends BaseObservable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WaitForHandle {
|
interface IWaitHandle<T> {
|
||||||
constructor(observable, predicate) {
|
promise: Promise<T>;
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WaitForHandle<T> implements IWaitHandle<T> {
|
||||||
|
private _promise: Promise<T>
|
||||||
|
private _reject: ((reason?: any) => void) | null;
|
||||||
|
private _subscription: (() => void) | null;
|
||||||
|
|
||||||
|
constructor(observable: BaseObservableValue<T>, predicate: (value: T) => boolean) {
|
||||||
this._promise = new Promise((resolve, reject) => {
|
this._promise = new Promise((resolve, reject) => {
|
||||||
this._reject = reject;
|
this._reject = reject;
|
||||||
this._subscription = observable.subscribe(v => {
|
this._subscription = observable.subscribe(v => {
|
||||||
|
@ -52,7 +59,7 @@ class WaitForHandle {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get promise() {
|
get promise(): Promise<T> {
|
||||||
return this._promise;
|
return this._promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,25 +75,24 @@ class WaitForHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResolvedWaitForHandle {
|
class ResolvedWaitForHandle<T> implements IWaitHandle<T> {
|
||||||
constructor(promise) {
|
constructor(public promise: Promise<T>) {}
|
||||||
this.promise = promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {}
|
dispose() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ObservableValue extends BaseObservableValue {
|
export class ObservableValue<T> extends BaseObservableValue<T> {
|
||||||
constructor(initialValue) {
|
private _value: T;
|
||||||
|
|
||||||
|
constructor(initialValue: T) {
|
||||||
super();
|
super();
|
||||||
this._value = initialValue;
|
this._value = initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
get() {
|
get(): T {
|
||||||
return this._value;
|
return this._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value: T): void {
|
||||||
if (value !== this._value) {
|
if (value !== this._value) {
|
||||||
this._value = value;
|
this._value = value;
|
||||||
this.emit(this._value);
|
this.emit(this._value);
|
||||||
|
@ -94,8 +100,10 @@ export class ObservableValue extends BaseObservableValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RetainedObservableValue extends ObservableValue {
|
export class RetainedObservableValue<T> extends ObservableValue<T> {
|
||||||
constructor(initialValue, freeCallback) {
|
private _freeCallback: () => void;
|
||||||
|
|
||||||
|
constructor(initialValue: T, freeCallback: () => void) {
|
||||||
super(initialValue);
|
super(initialValue);
|
||||||
this._freeCallback = freeCallback;
|
this._freeCallback = freeCallback;
|
||||||
}
|
}
|
||||||
|
@ -109,7 +117,7 @@ export class RetainedObservableValue extends ObservableValue {
|
||||||
export function tests() {
|
export function tests() {
|
||||||
return {
|
return {
|
||||||
"set emits an update": assert => {
|
"set emits an update": assert => {
|
||||||
const a = new ObservableValue();
|
const a = new ObservableValue<number>(0);
|
||||||
let fired = false;
|
let fired = false;
|
||||||
const subscription = a.subscribe(v => {
|
const subscription = a.subscribe(v => {
|
||||||
fired = true;
|
fired = true;
|
||||||
|
@ -140,7 +148,7 @@ export function tests() {
|
||||||
assert.strictEqual(a.get(), 6);
|
assert.strictEqual(a.get(), 6);
|
||||||
},
|
},
|
||||||
"waitFor promise rejects when disposed": async assert => {
|
"waitFor promise rejects when disposed": async assert => {
|
||||||
const a = new ObservableValue();
|
const a = new ObservableValue<number>(0);
|
||||||
const handle = a.waitFor(() => false);
|
const handle = a.waitFor(() => false);
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
handle.dispose();
|
handle.dispose();
|
|
@ -15,7 +15,7 @@ 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.js";
|
import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList";
|
||||||
|
|
||||||
export class AsyncMappedList extends BaseMappedList {
|
export class AsyncMappedList extends BaseMappedList {
|
||||||
constructor(sourceList, mapper, updater, removeCallback) {
|
constructor(sourceList, mapper, updater, removeCallback) {
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
|
||||||
import {findAndUpdateInArray} from "./common.js";
|
|
||||||
|
|
||||||
export class BaseMappedList extends BaseObservableList {
|
|
||||||
constructor(sourceList, mapper, updater, removeCallback) {
|
|
||||||
super();
|
|
||||||
this._sourceList = sourceList;
|
|
||||||
this._mapper = mapper;
|
|
||||||
this._updater = updater;
|
|
||||||
this._removeCallback = removeCallback;
|
|
||||||
this._mappedValues = null;
|
|
||||||
this._sourceUnsubscribe = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
findAndUpdate(predicate, updater) {
|
|
||||||
return findAndUpdateInArray(predicate, this._mappedValues, this, updater);
|
|
||||||
}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
return this._mappedValues.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this._mappedValues.values();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runAdd(list, index, mappedValue) {
|
|
||||||
list._mappedValues.splice(index, 0, mappedValue);
|
|
||||||
list.emitAdd(index, mappedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runUpdate(list, index, value, params) {
|
|
||||||
const mappedValue = list._mappedValues[index];
|
|
||||||
if (list._updater) {
|
|
||||||
list._updater(mappedValue, params, value);
|
|
||||||
}
|
|
||||||
list.emitUpdate(index, mappedValue, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runRemove(list, index) {
|
|
||||||
const mappedValue = list._mappedValues[index];
|
|
||||||
list._mappedValues.splice(index, 1);
|
|
||||||
if (list._removeCallback) {
|
|
||||||
list._removeCallback(mappedValue);
|
|
||||||
}
|
|
||||||
list.emitRemove(index, mappedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runMove(list, fromIdx, toIdx) {
|
|
||||||
const mappedValue = list._mappedValues[fromIdx];
|
|
||||||
list._mappedValues.splice(fromIdx, 1);
|
|
||||||
list._mappedValues.splice(toIdx, 0, mappedValue);
|
|
||||||
list.emitMove(fromIdx, toIdx, mappedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function runReset(list) {
|
|
||||||
list._mappedValues = [];
|
|
||||||
list.emitReset();
|
|
||||||
}
|
|
85
src/observable/list/BaseMappedList.ts
Normal file
85
src/observable/list/BaseMappedList.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
|
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> {
|
||||||
|
protected _sourceList: BaseObservableList<F>;
|
||||||
|
protected _sourceUnsubscribe: (() => void) | null = null;
|
||||||
|
_mapper: Mapper<F,T>;
|
||||||
|
_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) {
|
||||||
|
super();
|
||||||
|
this._sourceList = sourceList;
|
||||||
|
this._mapper = mapper;
|
||||||
|
this._updater = updater;
|
||||||
|
this._removeCallback = removeCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
findAndUpdate(predicate: (value: T) => boolean, updater: (value: T) => any | false) {
|
||||||
|
return findAndUpdateInArray(predicate, this._mappedValues!, this, updater);
|
||||||
|
}
|
||||||
|
|
||||||
|
get length() {
|
||||||
|
return this._mappedValues!.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this._mappedValues!.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runAdd<F,T>(list: BaseMappedList<F,T>, 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 {
|
||||||
|
const mappedValue = list._mappedValues![index];
|
||||||
|
if (list._updater) {
|
||||||
|
list._updater(mappedValue, params, value);
|
||||||
|
}
|
||||||
|
list.emitUpdate(index, mappedValue, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runRemove<F,T>(list: BaseMappedList<F,T>, index: number): void {
|
||||||
|
const mappedValue = list._mappedValues![index];
|
||||||
|
list._mappedValues!.splice(index, 1);
|
||||||
|
if (list._removeCallback) {
|
||||||
|
list._removeCallback(mappedValue);
|
||||||
|
}
|
||||||
|
list.emitRemove(index, mappedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runMove<F,T>(list: BaseMappedList<F,T>, 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 {
|
||||||
|
list._mappedValues = [];
|
||||||
|
list.emitReset();
|
||||||
|
}
|
|
@ -14,9 +14,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservable} from "../BaseObservable.js";
|
import {BaseObservable} from "../BaseObservable";
|
||||||
|
|
||||||
export class BaseObservableList extends BaseObservable {
|
export interface IListObserver<T> {
|
||||||
|
onReset(list: BaseObservableList<T>): void;
|
||||||
|
onAdd(index: number, value:T, list: BaseObservableList<T>): void;
|
||||||
|
onUpdate(index: number, value: T, params: any, list: BaseObservableList<T>): void;
|
||||||
|
onRemove(index: 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>> {
|
||||||
emitReset() {
|
emitReset() {
|
||||||
for(let h of this._handlers) {
|
for(let h of this._handlers) {
|
||||||
h.onReset(this);
|
h.onReset(this);
|
||||||
|
@ -24,19 +32,19 @@ export class BaseObservableList extends BaseObservable {
|
||||||
}
|
}
|
||||||
// we need batch events, mostly on index based collection though?
|
// we need batch events, mostly on index based collection though?
|
||||||
// maybe we should get started without?
|
// maybe we should get started without?
|
||||||
emitAdd(index, value) {
|
emitAdd(index: number, value: T): void {
|
||||||
for(let h of this._handlers) {
|
for(let h of this._handlers) {
|
||||||
h.onAdd(index, value, this);
|
h.onAdd(index, value, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitUpdate(index, value, params) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitRemove(index, value) {
|
emitRemove(index: number, value: T): void {
|
||||||
for(let h of this._handlers) {
|
for(let h of this._handlers) {
|
||||||
h.onRemove(index, value, this);
|
h.onRemove(index, value, this);
|
||||||
}
|
}
|
||||||
|
@ -44,17 +52,12 @@ export class BaseObservableList extends BaseObservable {
|
||||||
|
|
||||||
// toIdx assumes the item has already
|
// toIdx assumes the item has already
|
||||||
// been removed from its fromIdx
|
// been removed from its fromIdx
|
||||||
emitMove(fromIdx, toIdx, value) {
|
emitMove(fromIdx: number, toIdx: number, value: T): void {
|
||||||
for(let h of this._handlers) {
|
for(let h of this._handlers) {
|
||||||
h.onMove(fromIdx, toIdx, value, this);
|
h.onMove(fromIdx, toIdx, value, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
abstract [Symbol.iterator](): IterableIterator<T>;
|
||||||
throw new Error("unimplemented");
|
abstract get length(): number;
|
||||||
}
|
|
||||||
|
|
||||||
get length() {
|
|
||||||
throw new Error("unimplemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
|
|
||||||
export class ConcatList extends BaseObservableList {
|
export class ConcatList extends BaseObservableList {
|
||||||
constructor(...sourceLists) {
|
constructor(...sourceLists) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ 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.js";
|
import {BaseMappedList, runAdd, runUpdate, runRemove, runMove, runReset} from "./BaseMappedList";
|
||||||
|
|
||||||
export class MappedList extends BaseMappedList {
|
export class MappedList extends BaseMappedList {
|
||||||
onSubscribeFirst() {
|
onSubscribeFirst() {
|
||||||
|
@ -57,7 +57,7 @@ export class MappedList extends BaseMappedList {
|
||||||
}
|
}
|
||||||
|
|
||||||
import {ObservableArray} from "./ObservableArray.js";
|
import {ObservableArray} from "./ObservableArray.js";
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
|
|
||||||
export async function tests() {
|
export async function tests() {
|
||||||
class MockList extends BaseObservableList {
|
class MockList extends BaseObservableList {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
|
|
||||||
export class ObservableArray extends BaseObservableList {
|
export class ObservableArray extends BaseObservableList {
|
||||||
constructor(initialValues = []) {
|
constructor(initialValues = []) {
|
||||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
import {sortedIndex} from "../../utils/sortedIndex.js";
|
import {sortedIndex} from "../../utils/sortedIndex.js";
|
||||||
import {findAndUpdateInArray} from "./common.js";
|
import {findAndUpdateInArray} from "./common";
|
||||||
|
|
||||||
export class SortedArray extends BaseObservableList {
|
export class SortedArray extends BaseObservableList {
|
||||||
constructor(comparator) {
|
constructor(comparator) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableList} from "./BaseObservableList.js";
|
import {BaseObservableList} from "./BaseObservableList";
|
||||||
import {sortedIndex} from "../../utils/sortedIndex.js";
|
import {sortedIndex} from "../../utils/sortedIndex.js";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -14,9 +14,10 @@ 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 {BaseObservableList} from "./BaseObservableList";
|
||||||
|
|
||||||
/* inline update of item in collection backed by array, without replacing the preexising item */
|
/* inline update of item in collection backed by array, without replacing the preexising item */
|
||||||
export function findAndUpdateInArray(predicate, array, observable, updater) {
|
export function findAndUpdateInArray<T>(predicate: (value: T) => boolean, array: T[], observable: BaseObservableList<T>, updater: (value: T) => any | false) {
|
||||||
const index = array.findIndex(predicate);
|
const index = array.findIndex(predicate);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
const value = array[index];
|
const value = array[index];
|
||||||
|
@ -29,4 +30,4 @@ export function findAndUpdateInArray(predicate, array, observable, updater) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservable} from "../BaseObservable.js";
|
import {BaseObservable} from "../BaseObservable";
|
||||||
|
|
||||||
export class BaseObservableMap extends BaseObservable {
|
export class BaseObservableMap extends BaseObservable {
|
||||||
emitReset() {
|
emitReset() {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableValue} from "../../../observable/ObservableValue.js";
|
import {BaseObservableValue} from "../../../observable/ObservableValue";
|
||||||
|
|
||||||
export class History extends BaseObservableValue {
|
export class History extends BaseObservableValue {
|
||||||
handleEvent(event) {
|
handleEvent(event) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {BaseObservableValue} from "../../../observable/ObservableValue.js";
|
import {BaseObservableValue} from "../../../observable/ObservableValue";
|
||||||
|
|
||||||
export class OnlineStatus extends BaseObservableValue {
|
export class OnlineStatus extends BaseObservableValue {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
import {el} from "./html";
|
import {el} from "./html";
|
||||||
import {mountView, insertAt} from "./utils";
|
import {mountView, insertAt} from "./utils";
|
||||||
import {BaseObservableList as ObservableList} from "../../../../observable/list/BaseObservableList.js";
|
import {SubscriptionHandle} from "../../../../observable/BaseObservable";
|
||||||
|
import {BaseObservableList as ObservableList} from "../../../../observable/list/BaseObservableList";
|
||||||
import {IView, IMountArgs} from "./types";
|
import {IView, IMountArgs} from "./types";
|
||||||
|
|
||||||
interface IOptions<T, V> {
|
interface IOptions<T, V> {
|
||||||
|
@ -27,8 +28,6 @@ interface IOptions<T, V> {
|
||||||
parentProvidesUpdates?: boolean
|
parentProvidesUpdates?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubscriptionHandle = () => undefined;
|
|
||||||
|
|
||||||
export class ListView<T, V extends IView> implements IView {
|
export class ListView<T, V extends IView> implements IView {
|
||||||
|
|
||||||
private _onItemClick?: (childView: V, evt: UIEvent) => void;
|
private _onItemClick?: (childView: V, evt: UIEvent) => void;
|
||||||
|
@ -137,26 +136,34 @@ export class ListView<T, V extends IView> implements IView {
|
||||||
this._root!.appendChild(fragment);
|
this._root!.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onAdd(idx: number, value: T) {
|
onReset() {
|
||||||
|
for (const child of this._childInstances!) {
|
||||||
|
child.root()!.remove();
|
||||||
|
child.unmount();
|
||||||
|
}
|
||||||
|
this._childInstances!.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(idx: number, value: T) {
|
||||||
const child = this._childCreator(value);
|
const child = this._childCreator(value);
|
||||||
this._childInstances!.splice(idx, 0, child);
|
this._childInstances!.splice(idx, 0, child);
|
||||||
insertAt(this._root!, idx, mountView(child, this._mountArgs));
|
insertAt(this._root!, idx, mountView(child, this._mountArgs));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onRemove(idx: number, value: T) {
|
onRemove(idx: number, value: T) {
|
||||||
const [child] = this._childInstances!.splice(idx, 1);
|
const [child] = this._childInstances!.splice(idx, 1);
|
||||||
child.root()!.remove();
|
child.root()!.remove();
|
||||||
child.unmount();
|
child.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onMove(fromIdx: number, toIdx: number, value: T) {
|
onMove(fromIdx: number, toIdx: number, value: T) {
|
||||||
const [child] = this._childInstances!.splice(fromIdx, 1);
|
const [child] = this._childInstances!.splice(fromIdx, 1);
|
||||||
this._childInstances!.splice(toIdx, 0, child);
|
this._childInstances!.splice(toIdx, 0, child);
|
||||||
child.root()!.remove();
|
child.root()!.remove();
|
||||||
insertAt(this._root!, toIdx, child.root()! as Element);
|
insertAt(this._root!, toIdx, child.root()! as Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onUpdate(i: number, value: T, params: any) {
|
onUpdate(i: number, value: T, params: any) {
|
||||||
if (this._childInstances) {
|
if (this._childInstances) {
|
||||||
const instance = this._childInstances![i];
|
const instance = this._childInstances![i];
|
||||||
instance && instance.update(value, params);
|
instance && instance.update(value, params);
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
|
||||||
import {AnnouncementView} from "./timeline/AnnouncementView.js";
|
import {AnnouncementView} from "./timeline/AnnouncementView.js";
|
||||||
import {RedactedView} from "./timeline/RedactedView.js";
|
import {RedactedView} from "./timeline/RedactedView.js";
|
||||||
import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
|
import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
|
||||||
import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList.js";
|
import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList";
|
||||||
|
|
||||||
//import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
|
//import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
|
||||||
export interface TimelineViewModel extends IObservableValue {
|
export interface TimelineViewModel extends IObservableValue {
|
||||||
|
@ -211,7 +211,12 @@ class TilesListView extends ListView<SimpleTile, TileView> {
|
||||||
this.onChanged = onChanged;
|
this.onChanged = onChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onUpdate(index: number, value: SimpleTile, param: any) {
|
override onReset() {
|
||||||
|
super.onReset();
|
||||||
|
this.onChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
override onUpdate(index: number, value: SimpleTile, param: any) {
|
||||||
if (param === "shape") {
|
if (param === "shape") {
|
||||||
const ExpectedClass = viewClassForEntry(value);
|
const ExpectedClass = viewClassForEntry(value);
|
||||||
const child = this.getChildInstanceByIndex(index);
|
const child = this.getChildInstanceByIndex(index);
|
||||||
|
@ -227,17 +232,17 @@ class TilesListView extends ListView<SimpleTile, TileView> {
|
||||||
this.onChanged();
|
this.onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onAdd(idx: number, value: SimpleTile) {
|
override onAdd(idx: number, value: SimpleTile) {
|
||||||
super.onAdd(idx, value);
|
super.onAdd(idx, value);
|
||||||
this.onChanged();
|
this.onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onRemove(idx: number, value: SimpleTile) {
|
override onRemove(idx: number, value: SimpleTile) {
|
||||||
super.onRemove(idx, value);
|
super.onRemove(idx, value);
|
||||||
this.onChanged();
|
this.onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onMove(fromIdx: number, toIdx: number, value: SimpleTile) {
|
override onMove(fromIdx: number, toIdx: number, value: SimpleTile) {
|
||||||
super.onMove(fromIdx, toIdx, value);
|
super.onMove(fromIdx, toIdx, value);
|
||||||
this.onChanged();
|
this.onChanged();
|
||||||
}
|
}
|
||||||
|
|
58
src/sdk.ts
Normal file
58
src/sdk.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay.js";
|
||||||
|
import {SessionContainer} from "./matrix/SessionContainer.js";
|
||||||
|
import {RootViewModel} from "./domain/RootViewModel.js";
|
||||||
|
import {createNavigation, createRouter} from "./domain/navigation/index.js";
|
||||||
|
// Don't use a default export here, as we use multiple entries during legacy build,
|
||||||
|
// which does not support default exports,
|
||||||
|
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
|
||||||
|
export async function main(platform) {
|
||||||
|
try {
|
||||||
|
// to replay:
|
||||||
|
// const fetchLog = await (await fetch("/fetchlogs/constrainterror.json")).json();
|
||||||
|
// const replay = new ReplayRequester(fetchLog, {delay: false});
|
||||||
|
// const request = replay.request;
|
||||||
|
|
||||||
|
// to record:
|
||||||
|
// const recorder = new RecordRequester(createFetchRequest(clock.createTimeout));
|
||||||
|
// const request = recorder.request;
|
||||||
|
// window.getBrawlFetchLog = () => recorder.log();
|
||||||
|
const navigation = createNavigation();
|
||||||
|
platform.setNavigation(navigation);
|
||||||
|
const urlRouter = createRouter({navigation, history: platform.history});
|
||||||
|
urlRouter.attach();
|
||||||
|
const olmPromise = platform.loadOlm();
|
||||||
|
const workerPromise = platform.loadOlmWorker();
|
||||||
|
|
||||||
|
const vm = new RootViewModel({
|
||||||
|
createSessionContainer: () => {
|
||||||
|
return new SessionContainer({platform, olmPromise, workerPromise});
|
||||||
|
},
|
||||||
|
platform,
|
||||||
|
// the only public interface of the router is to create urls,
|
||||||
|
// so we call it that in the view models
|
||||||
|
urlCreator: urlRouter,
|
||||||
|
navigation,
|
||||||
|
});
|
||||||
|
await vm.load();
|
||||||
|
platform.createAndMountRootView(vm);
|
||||||
|
} catch(err) {
|
||||||
|
console.error(`${err.message}:\n${err.stack}`);
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue