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

Typescript conversion of base observables
This commit is contained in:
Bruno Windels 2021-10-01 10:10:20 +02:00 committed by GitHub
commit de22a0790f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 259 additions and 170 deletions

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 {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue.js"; import {BaseObservableValue, ObservableValue} from "../../observable/ObservableValue";
export class Navigation { export class Navigation {
constructor(allowsChild) { constructor(allowsChild) {

View file

@ -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 {

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 {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();
} }
} }

View file

@ -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() {

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 {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

View file

@ -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";

View file

@ -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";

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {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;

View file

@ -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",

View file

@ -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";

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 {BaseObservableValue} from "../../observable/ObservableValue.js"; import {BaseObservableValue} from "../../observable/ObservableValue";
export class ObservedEventMap { export class ObservedEventMap {
constructor(notifyEmpty) { constructor(notifyEmpty) {

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 {ObservableValue} from "../observable/ObservableValue.js"; import {ObservableValue} from "../observable/ObservableValue";
class Timeout { class Timeout {
constructor(elapsed, ms) { constructor(elapsed, ms) {

View file

@ -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; }
} }

View file

@ -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();

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {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) {

View file

@ -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();
}

View 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();
}

View file

@ -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");
}
} }

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 {BaseObservableList} from "./BaseObservableList.js"; import {BaseObservableList} from "./BaseObservableList";
export class ConcatList extends BaseObservableList { export class ConcatList extends BaseObservableList {
constructor(...sourceLists) { constructor(...sourceLists) {

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {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 {

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 {BaseObservableList} from "./BaseObservableList.js"; import {BaseObservableList} from "./BaseObservableList";
export class ObservableArray extends BaseObservableList { export class ObservableArray extends BaseObservableList {
constructor(initialValues = []) { constructor(initialValues = []) {

View file

@ -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) {

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 {BaseObservableList} from "./BaseObservableList.js"; import {BaseObservableList} from "./BaseObservableList";
import {sortedIndex} from "../../utils/sortedIndex.js"; import {sortedIndex} from "../../utils/sortedIndex.js";
/* /*

View file

@ -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;
} }

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 {BaseObservable} from "../BaseObservable.js"; import {BaseObservable} from "../BaseObservable";
export class BaseObservableMap extends BaseObservable { export class BaseObservableMap extends BaseObservable {
emitReset() { emitReset() {

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 {BaseObservableValue} from "../../../observable/ObservableValue.js"; import {BaseObservableValue} from "../../../observable/ObservableValue";
export class History extends BaseObservableValue { export class History extends BaseObservableValue {
handleEvent(event) { handleEvent(event) {

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 {BaseObservableValue} from "../../../observable/ObservableValue.js"; import {BaseObservableValue} from "../../../observable/ObservableValue";
export class OnlineStatus extends BaseObservableValue { export class OnlineStatus extends BaseObservableValue {
constructor() { constructor() {

View file

@ -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);

View file

@ -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
View 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}`);
}
}