2020-08-05 22:08:55 +05:30
|
|
|
/*
|
|
|
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
2022-02-14 22:20:17 +05:30
|
|
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
2020-08-05 22:08:55 +05:30
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2020-04-18 22:46:16 +05:30
|
|
|
// ViewModel should just be an eventemitter, not an ObservableValue
|
|
|
|
// as in some cases it would really be more convenient to have multiple events (like telling the timeline to scroll down)
|
|
|
|
// we do need to return a disposable from EventEmitter.on, or at least have a method here to easily track a subscription to an EventEmitter
|
|
|
|
|
2021-09-16 13:53:03 +05:30
|
|
|
import {EventEmitter} from "../utils/EventEmitter";
|
2021-11-16 14:13:35 +05:30
|
|
|
import {Disposables} from "../utils/Disposables";
|
2020-05-04 22:53:11 +05:30
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
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";
|
|
|
|
|
2022-02-25 16:45:07 +05:30
|
|
|
export type Options = {
|
2022-02-14 22:20:17 +05:30
|
|
|
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) {
|
2020-04-10 02:49:49 +05:30
|
|
|
super();
|
2020-10-06 21:35:02 +05:30
|
|
|
this._options = options;
|
2020-04-10 02:49:49 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
childOptions<T extends Object>(explicitOptions: T): T & Options {
|
|
|
|
return Object.assign({}, this._options, explicitOptions);
|
2020-04-10 02:49:49 +05:30
|
|
|
}
|
|
|
|
|
2022-02-17 13:54:18 +05:30
|
|
|
get options(): O { return this._options; }
|
|
|
|
|
2020-10-16 21:36:20 +05:30
|
|
|
// makes it easier to pass through dependencies of a sub-view model
|
2022-02-14 22:20:17 +05:30
|
|
|
getOption<N extends keyof O>(name: N): O[N] {
|
2020-10-16 21:36:20 +05:30
|
|
|
return this._options[name];
|
|
|
|
}
|
|
|
|
|
2022-03-07 11:33:51 +05:30
|
|
|
observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void) {
|
2022-03-03 15:36:25 +05:30
|
|
|
const segmentObservable = this.navigation.observe(type);
|
2022-03-07 11:33:51 +05:30
|
|
|
const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => {
|
2022-03-03 15:36:25 +05:30
|
|
|
onChange(value, type);
|
|
|
|
})
|
|
|
|
this.track(unsubscribe);
|
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
track<D extends Disposable>(disposable: D): D {
|
2020-05-04 22:53:11 +05:30
|
|
|
if (!this.disposables) {
|
|
|
|
this.disposables = new Disposables();
|
|
|
|
}
|
2020-09-10 20:10:30 +05:30
|
|
|
return this.disposables.track(disposable);
|
2020-04-10 02:49:49 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
untrack(disposable: Disposable): undefined {
|
2020-10-07 15:55:03 +05:30
|
|
|
if (this.disposables) {
|
|
|
|
return this.disposables.untrack(disposable);
|
|
|
|
}
|
2022-02-14 22:20:17 +05:30
|
|
|
return undefined;
|
2020-10-07 15:55:03 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
dispose(): void {
|
2020-05-04 22:53:11 +05:30
|
|
|
if (this.disposables) {
|
|
|
|
this.disposables.dispose();
|
|
|
|
}
|
2020-09-14 21:13:06 +05:30
|
|
|
this._isDisposed = true;
|
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get isDisposed(): boolean {
|
2020-09-14 21:13:06 +05:30
|
|
|
return this._isDisposed;
|
2020-05-04 22:53:11 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
disposeTracked(disposable: Disposable | undefined): undefined {
|
2020-05-06 02:44:58 +05:30
|
|
|
if (this.disposables) {
|
|
|
|
return this.disposables.disposeTracked(disposable);
|
|
|
|
}
|
2022-02-14 22:20:17 +05:30
|
|
|
return undefined;
|
2020-05-06 02:44:58 +05:30
|
|
|
}
|
|
|
|
|
2020-05-04 22:53:11 +05:30
|
|
|
// TODO: this will need to support binding
|
|
|
|
// if any of the expr is a function, assume the function is a binding, and return a binding function ourselves
|
2020-05-06 02:44:58 +05:30
|
|
|
//
|
|
|
|
// 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.
|
2022-02-25 16:45:07 +05:30
|
|
|
i18n(parts: TemplateStringsArray, ...expr: any[]) {
|
2020-05-04 22:53:11 +05:30
|
|
|
// just concat for now
|
2021-02-16 20:08:43 +05:30
|
|
|
let result = "";
|
|
|
|
for (let i = 0; i < parts.length; ++i) {
|
|
|
|
result = result + parts[i];
|
|
|
|
if (i < expr.length) {
|
|
|
|
result = result + expr[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2020-05-04 22:53:11 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
updateOptions(options: O): void {
|
2020-08-17 18:41:39 +05:30
|
|
|
this._options = Object.assign(this._options, options);
|
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
emitChange(changedProps: any): void {
|
2022-02-17 13:54:18 +05:30
|
|
|
if (this._options.emitChange) {
|
2020-08-12 21:09:11 +05:30
|
|
|
this._options.emitChange(changedProps);
|
|
|
|
} else {
|
|
|
|
this.emit("change", changedProps);
|
|
|
|
}
|
2020-04-10 02:49:49 +05:30
|
|
|
}
|
2020-05-06 02:44:58 +05:30
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get platform(): Platform {
|
2020-10-26 20:14:11 +05:30
|
|
|
return this._options.platform;
|
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get clock(): Clock {
|
2020-10-26 20:14:11 +05:30
|
|
|
return this._options.platform.clock;
|
2020-05-06 02:44:58 +05:30
|
|
|
}
|
2020-10-06 21:35:02 +05:30
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get logger(): ILogger {
|
2021-02-12 22:36:14 +05:30
|
|
|
return this.platform.logger;
|
2021-02-12 01:37:18 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get urlCreator(): URLRouter {
|
2020-10-16 16:32:21 +05:30
|
|
|
return this._options.urlCreator;
|
2020-10-06 21:35:02 +05:30
|
|
|
}
|
|
|
|
|
2022-02-14 22:20:17 +05:30
|
|
|
get navigation(): Navigation {
|
2020-10-06 21:35:02 +05:30
|
|
|
return this._options.navigation;
|
|
|
|
}
|
2020-04-10 02:49:49 +05:30
|
|
|
}
|