convert ViewModel to typescript

This commit is contained in:
Bruno Windels 2022-02-14 17:50:17 +01:00
parent 460780d602
commit 4d82dd22b6
2 changed files with 52 additions and 38 deletions

View file

@ -1,5 +1,6 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2022 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.
@ -21,54 +22,70 @@ limitations under the License.
import {EventEmitter} from "../utils/EventEmitter";
import {Disposables} from "../utils/Disposables";
export class ViewModel extends EventEmitter {
constructor(options = {}) {
import type {Disposable} from "../utils/Disposables";
import type {Platform} from "../platform/web/Platform";
import type {Clock} from "../platform/web/dom/Clock";
import type {ILogger} from "../logging/types";
import type {Navigation} from "./navigation/Navigation";
import type {URLRouter} from "./navigation/URLRouter";
type Options = {
platform: Platform
logger: ILogger
urlCreator: URLRouter
navigation: Navigation
emitChange?: (params: any) => void
}
export class ViewModel<O extends Options = Options> extends EventEmitter<{change: never}> {
private disposables?: Disposables;
private _isDisposed = false;
private _options: O;
constructor(options: O) {
super();
this.disposables = null;
this._isDisposed = false;
this._options = options;
}
childOptions(explicitOptions) {
const {navigation, urlCreator, platform} = this._options;
return Object.assign({navigation, urlCreator, platform}, explicitOptions);
childOptions<T extends Object>(explicitOptions: T): T & Options {
return Object.assign({}, this._options, explicitOptions);
}
// makes it easier to pass through dependencies of a sub-view model
getOption(name) {
getOption<N extends keyof O>(name: N): O[N] {
return this._options[name];
}
track(disposable) {
track<D extends Disposable>(disposable: D): D {
if (!this.disposables) {
this.disposables = new Disposables();
}
return this.disposables.track(disposable);
}
untrack(disposable) {
untrack(disposable: Disposable): undefined {
if (this.disposables) {
return this.disposables.untrack(disposable);
}
return null;
return undefined;
}
dispose() {
dispose(): void {
if (this.disposables) {
this.disposables.dispose();
}
this._isDisposed = true;
}
get isDisposed() {
get isDisposed(): boolean {
return this._isDisposed;
}
disposeTracked(disposable) {
disposeTracked(disposable: Disposable | undefined): undefined {
if (this.disposables) {
return this.disposables.disposeTracked(disposable);
}
return null;
return undefined;
}
// TODO: this will need to support binding
@ -76,7 +93,7 @@ export class ViewModel extends EventEmitter {
//
// translated string should probably always be bindings, unless we're fine with a refresh when changing the language?
// we probably are, if we're using routing with a url, we could just refresh.
i18n(parts, ...expr) {
i18n(parts: string[], ...expr: any[]) {
// just concat for now
let result = "";
for (let i = 0; i < parts.length; ++i) {
@ -88,11 +105,11 @@ export class ViewModel extends EventEmitter {
return result;
}
updateOptions(options) {
updateOptions(options: O): void {
this._options = Object.assign(this._options, options);
}
emitChange(changedProps) {
emitChange(changedProps: any): void {
if (this._options.emitChange) {
this._options.emitChange(changedProps);
} else {
@ -100,27 +117,23 @@ export class ViewModel extends EventEmitter {
}
}
get platform() {
get platform(): Platform {
return this._options.platform;
}
get clock() {
get clock(): Clock {
return this._options.platform.clock;
}
get logger() {
get logger(): ILogger {
return this.platform.logger;
}
/**
* The url router, only meant to be used to create urls with from view models.
* @return {URLRouter}
*/
get urlCreator() {
get urlCreator(): URLRouter {
return this._options.urlCreator;
}
get navigation() {
get navigation(): Navigation {
return this._options.navigation;
}
}

View file

@ -1,5 +1,6 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Copyright 2022 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.
@ -18,7 +19,7 @@ export interface IDisposable {
dispose(): void;
}
type Disposable = IDisposable | (() => void);
export type Disposable = IDisposable | (() => void);
function disposeValue(value: Disposable): void {
if (typeof value === "function") {
@ -33,9 +34,9 @@ function isDisposable(value: Disposable): boolean {
}
export class Disposables {
private _disposables: Disposable[] | null = [];
private _disposables?: Disposable[] = [];
track(disposable: Disposable): Disposable {
track<D extends Disposable>(disposable: D): D {
if (!isDisposable(disposable)) {
throw new Error("Not a disposable");
}
@ -48,16 +49,16 @@ export class Disposables {
return disposable;
}
untrack(disposable: Disposable): null {
untrack(disposable: Disposable): undefined {
if (this.isDisposed) {
console.warn("Disposables already disposed, cannot untrack");
return null;
return undefined;
}
const idx = this._disposables!.indexOf(disposable);
if (idx >= 0) {
this._disposables!.splice(idx, 1);
}
return null;
return undefined;
}
dispose(): void {
@ -65,17 +66,17 @@ export class Disposables {
for (const d of this._disposables) {
disposeValue(d);
}
this._disposables = null;
this._disposables = undefined;
}
}
get isDisposed(): boolean {
return this._disposables === null;
return this._disposables === undefined;
}
disposeTracked(value: Disposable): null {
disposeTracked(value: Disposable | undefined): undefined {
if (value === undefined || value === null || this.isDisposed) {
return null;
return undefined;
}
const idx = this._disposables!.indexOf(value);
if (idx !== -1) {
@ -84,6 +85,6 @@ export class Disposables {
} else {
console.warn("disposable not found, did it leak?", value);
}
return null;
return undefined;
}
}