Merge pull request #643 from vector-im/bwindels/separate-logout-view
Show logout in separate view so it's clear something is happening
This commit is contained in:
commit
454d2d3666
13 changed files with 198 additions and 46 deletions
63
src/domain/LogoutViewModel.js
Normal file
63
src/domain/LogoutViewModel.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
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 {ViewModel} from "./ViewModel.js";
|
||||||
|
import {Client} from "../matrix/Client.js";
|
||||||
|
|
||||||
|
export class LogoutViewModel extends ViewModel {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this._sessionId = options.sessionId;
|
||||||
|
this._busy = false;
|
||||||
|
this._showConfirm = true;
|
||||||
|
this._error = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showConfirm() {
|
||||||
|
return this._showConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
get busy() {
|
||||||
|
return this._busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cancelUrl() {
|
||||||
|
return this.urlCreator.urlForSegment("session", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
this._busy = true;
|
||||||
|
this._showConfirm = false;
|
||||||
|
this.emitChange("busy");
|
||||||
|
try {
|
||||||
|
const client = new Client(this.platform);
|
||||||
|
await client.startLogout(this._sessionId);
|
||||||
|
this.navigation.push("session", true);
|
||||||
|
} catch (err) {
|
||||||
|
this._error = err;
|
||||||
|
this._busy = false;
|
||||||
|
this.emitChange("busy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
if (this._error) {
|
||||||
|
return this.i18n`Could not log out of device: ${this._error.message}`;
|
||||||
|
} else {
|
||||||
|
return this.i18n`Logging out… Please don't close the app.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import {Client} from "../matrix/Client.js";
|
||||||
import {SessionViewModel} from "./session/SessionViewModel.js";
|
import {SessionViewModel} from "./session/SessionViewModel.js";
|
||||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||||
import {LoginViewModel} from "./login/LoginViewModel.js";
|
import {LoginViewModel} from "./login/LoginViewModel.js";
|
||||||
|
import {LogoutViewModel} from "./LogoutViewModel.js";
|
||||||
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
||||||
import {ViewModel} from "./ViewModel.js";
|
import {ViewModel} from "./ViewModel.js";
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ export class RootViewModel extends ViewModel {
|
||||||
this._sessionPickerViewModel = null;
|
this._sessionPickerViewModel = null;
|
||||||
this._sessionLoadViewModel = null;
|
this._sessionLoadViewModel = null;
|
||||||
this._loginViewModel = null;
|
this._loginViewModel = null;
|
||||||
|
this._logoutViewModel = null;
|
||||||
this._sessionViewModel = null;
|
this._sessionViewModel = null;
|
||||||
this._pendingClient = null;
|
this._pendingClient = null;
|
||||||
}
|
}
|
||||||
|
@ -40,13 +42,18 @@ export class RootViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _applyNavigation(shouldRestoreLastUrl) {
|
async _applyNavigation(shouldRestoreLastUrl) {
|
||||||
const isLogin = this.navigation.path.get("login")
|
const isLogin = this.navigation.path.get("login");
|
||||||
|
const logoutSessionId = this.navigation.path.get("logout")?.value;
|
||||||
const sessionId = this.navigation.path.get("session")?.value;
|
const sessionId = this.navigation.path.get("session")?.value;
|
||||||
const loginToken = this.navigation.path.get("sso")?.value;
|
const loginToken = this.navigation.path.get("sso")?.value;
|
||||||
if (isLogin) {
|
if (isLogin) {
|
||||||
if (this.activeSection !== "login") {
|
if (this.activeSection !== "login") {
|
||||||
this._showLogin();
|
this._showLogin();
|
||||||
}
|
}
|
||||||
|
} else if (logoutSessionId) {
|
||||||
|
if (this.activeSection !== "logout") {
|
||||||
|
this._showLogout(logoutSessionId);
|
||||||
|
}
|
||||||
} else if (sessionId === true) {
|
} else if (sessionId === true) {
|
||||||
if (this.activeSection !== "picker") {
|
if (this.activeSection !== "picker") {
|
||||||
this._showPicker();
|
this._showPicker();
|
||||||
|
@ -123,6 +130,12 @@ export class RootViewModel extends ViewModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_showLogout(sessionId) {
|
||||||
|
this._setSection(() => {
|
||||||
|
this._logoutViewModel = new LogoutViewModel(this.childOptions({sessionId}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
_showSession(client) {
|
_showSession(client) {
|
||||||
this._setSection(() => {
|
this._setSection(() => {
|
||||||
this._sessionViewModel = new SessionViewModel(this.childOptions({client}));
|
this._sessionViewModel = new SessionViewModel(this.childOptions({client}));
|
||||||
|
@ -149,6 +162,8 @@ export class RootViewModel extends ViewModel {
|
||||||
return "session";
|
return "session";
|
||||||
} else if (this._loginViewModel) {
|
} else if (this._loginViewModel) {
|
||||||
return "login";
|
return "login";
|
||||||
|
} else if (this._logoutViewModel) {
|
||||||
|
return "logout";
|
||||||
} else if (this._sessionPickerViewModel) {
|
} else if (this._sessionPickerViewModel) {
|
||||||
return "picker";
|
return "picker";
|
||||||
} else if (this._sessionLoadViewModel) {
|
} else if (this._sessionLoadViewModel) {
|
||||||
|
@ -164,12 +179,14 @@ export class RootViewModel extends ViewModel {
|
||||||
this._sessionPickerViewModel = this.disposeTracked(this._sessionPickerViewModel);
|
this._sessionPickerViewModel = this.disposeTracked(this._sessionPickerViewModel);
|
||||||
this._sessionLoadViewModel = this.disposeTracked(this._sessionLoadViewModel);
|
this._sessionLoadViewModel = this.disposeTracked(this._sessionLoadViewModel);
|
||||||
this._loginViewModel = this.disposeTracked(this._loginViewModel);
|
this._loginViewModel = this.disposeTracked(this._loginViewModel);
|
||||||
|
this._logoutViewModel = this.disposeTracked(this._logoutViewModel);
|
||||||
this._sessionViewModel = this.disposeTracked(this._sessionViewModel);
|
this._sessionViewModel = this.disposeTracked(this._sessionViewModel);
|
||||||
// now set it again
|
// now set it again
|
||||||
setter();
|
setter();
|
||||||
this._sessionPickerViewModel && this.track(this._sessionPickerViewModel);
|
this._sessionPickerViewModel && this.track(this._sessionPickerViewModel);
|
||||||
this._sessionLoadViewModel && this.track(this._sessionLoadViewModel);
|
this._sessionLoadViewModel && this.track(this._sessionLoadViewModel);
|
||||||
this._loginViewModel && this.track(this._loginViewModel);
|
this._loginViewModel && this.track(this._loginViewModel);
|
||||||
|
this._logoutViewModel && this.track(this._logoutViewModel);
|
||||||
this._sessionViewModel && this.track(this._sessionViewModel);
|
this._sessionViewModel && this.track(this._sessionViewModel);
|
||||||
this.emitChange("activeSection");
|
this.emitChange("activeSection");
|
||||||
}
|
}
|
||||||
|
@ -177,6 +194,7 @@ export class RootViewModel extends ViewModel {
|
||||||
get error() { return this._error; }
|
get error() { return this._error; }
|
||||||
get sessionViewModel() { return this._sessionViewModel; }
|
get sessionViewModel() { return this._sessionViewModel; }
|
||||||
get loginViewModel() { return this._loginViewModel; }
|
get loginViewModel() { return this._loginViewModel; }
|
||||||
|
get logoutViewModel() { return this._logoutViewModel; }
|
||||||
get sessionPickerViewModel() { return this._sessionPickerViewModel; }
|
get sessionPickerViewModel() { return this._sessionPickerViewModel; }
|
||||||
get sessionLoadViewModel() { return this._sessionLoadViewModel; }
|
get sessionLoadViewModel() { return this._sessionLoadViewModel; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ function allowsChild(parent, child) {
|
||||||
switch (parent?.type) {
|
switch (parent?.type) {
|
||||||
case undefined:
|
case undefined:
|
||||||
// allowed root segments
|
// allowed root segments
|
||||||
return type === "login" || type === "session" || type === "sso";
|
return type === "login" || type === "session" || type === "sso" || type === "logout";
|
||||||
case "session":
|
case "session":
|
||||||
return type === "room" || type === "rooms" || type === "settings";
|
return type === "room" || type === "rooms" || type === "settings";
|
||||||
case "rooms":
|
case "rooms":
|
||||||
|
|
|
@ -50,7 +50,6 @@ export class SettingsViewModel extends ViewModel {
|
||||||
this.minSentImageSizeLimit = 400;
|
this.minSentImageSizeLimit = 400;
|
||||||
this.maxSentImageSizeLimit = 4000;
|
this.maxSentImageSizeLimit = 4000;
|
||||||
this.pushNotifications = new PushNotificationStatus();
|
this.pushNotifications = new PushNotificationStatus();
|
||||||
this._isLoggingOut = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get _session() {
|
get _session() {
|
||||||
|
@ -58,14 +57,9 @@ export class SettingsViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
this._isLoggingOut = true;
|
this.navigation.push("logout", this._client.sessionId);
|
||||||
await this._client.logout();
|
|
||||||
this.emitChange("isLoggingOut");
|
|
||||||
this.navigation.push("session", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isLoggingOut() { return this._isLoggingOut; }
|
|
||||||
|
|
||||||
setSentImageSizeLimit(size) {
|
setSentImageSizeLimit(size) {
|
||||||
if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) {
|
if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) {
|
||||||
this.sentImageSizeLimit = null;
|
this.sentImageSizeLimit = null;
|
||||||
|
|
|
@ -386,10 +386,21 @@ export class Client {
|
||||||
return !this._reconnector;
|
return !this._reconnector;
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
startLogout(sessionId) {
|
||||||
return this._platform.logger.run("logout", async log => {
|
return this._platform.logger.run("logout", async log => {
|
||||||
|
this._sessionId = sessionId;
|
||||||
|
log.set("id", this._sessionId);
|
||||||
|
const sessionInfo = await this._platform.sessionInfoStorage.get(this._sessionId);
|
||||||
|
if (!sessionInfo) {
|
||||||
|
throw new Error(`Could not find session for id ${this._sessionId}`);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this._session?.logout(log);
|
const hsApi = new HomeServerApi({
|
||||||
|
homeserver: sessionInfo.homeServer,
|
||||||
|
accessToken: sessionInfo.accessToken,
|
||||||
|
request: this._platform.request
|
||||||
|
});
|
||||||
|
await hsApi.logout({log}).response();
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
await this.deleteSession(log);
|
await this.deleteSession(log);
|
||||||
});
|
});
|
||||||
|
|
|
@ -109,11 +109,6 @@ export class Session {
|
||||||
return this._sessionInfo.userId;
|
return this._sessionInfo.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal call Client.logout instead */
|
|
||||||
async logout(log = undefined) {
|
|
||||||
await this._hsApi.logout({log}).response();
|
|
||||||
}
|
|
||||||
|
|
||||||
// called once this._e2eeAccount is assigned
|
// called once this._e2eeAccount is assigned
|
||||||
_setupEncryption() {
|
_setupEncryption() {
|
||||||
// TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account
|
// TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account
|
||||||
|
|
53
src/platform/web/ui/LogoutView.js
Normal file
53
src/platform/web/ui/LogoutView.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
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 {TemplateView, InlineTemplateView} from "./general/TemplateView";
|
||||||
|
import {spinner} from "./common.js";
|
||||||
|
|
||||||
|
export class LogoutView extends TemplateView {
|
||||||
|
render(t, vm) {
|
||||||
|
const confirmView = new InlineTemplateView(vm, t => {
|
||||||
|
return t.div([
|
||||||
|
t.p("Are you sure you want to log out?"),
|
||||||
|
t.div({ className: "button-row" }, [
|
||||||
|
t.a({
|
||||||
|
className: "button-action",
|
||||||
|
type: "submit",
|
||||||
|
href: vm.cancelUrl,
|
||||||
|
}, ["Cancel"]),
|
||||||
|
t.button({
|
||||||
|
className: "button-action primary destructive",
|
||||||
|
type: "submit",
|
||||||
|
onClick: () => vm.logout(),
|
||||||
|
}, vm.i18n`Log out`)
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
const progressView = new InlineTemplateView(vm, t => {
|
||||||
|
return t.p({className: "status", hidden: vm => !vm.showStatus}, [
|
||||||
|
spinner(t, {hidden: vm => !vm.busy}), t.span(vm => vm.status)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return t.div({className: "LogoutScreen"}, [
|
||||||
|
t.div({className: "content"}, [
|
||||||
|
t.mapView(vm => vm.showConfirm, showConfirm => {
|
||||||
|
return showConfirm ? confirmView : progressView;
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {SessionView} from "./session/SessionView.js";
|
import {SessionView} from "./session/SessionView.js";
|
||||||
import {LoginView} from "./login/LoginView.js";
|
import {LoginView} from "./login/LoginView";
|
||||||
|
import {LogoutView} from "./LogoutView.js";
|
||||||
import {SessionLoadView} from "./login/SessionLoadView.js";
|
import {SessionLoadView} from "./login/SessionLoadView.js";
|
||||||
import {SessionPickerView} from "./login/SessionPickerView.js";
|
import {SessionPickerView} from "./login/SessionPickerView.js";
|
||||||
import {TemplateView} from "./general/TemplateView";
|
import {TemplateView} from "./general/TemplateView";
|
||||||
|
@ -36,6 +37,8 @@ export class RootView extends TemplateView {
|
||||||
return new SessionView(vm.sessionViewModel);
|
return new SessionView(vm.sessionViewModel);
|
||||||
case "login":
|
case "login":
|
||||||
return new LoginView(vm.loginViewModel);
|
return new LoginView(vm.loginViewModel);
|
||||||
|
case "logout":
|
||||||
|
return new LogoutView(vm.logoutViewModel);
|
||||||
case "picker":
|
case "picker":
|
||||||
return new SessionPickerView(vm.sessionPickerViewModel);
|
return new SessionPickerView(vm.sessionPickerViewModel);
|
||||||
case "redirecting":
|
case "redirecting":
|
||||||
|
|
|
@ -20,15 +20,16 @@ export function spinner(t, extraClasses = undefined) {
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
container = document.querySelector(".hydrogen");
|
container = document.querySelector(".hydrogen");
|
||||||
}
|
}
|
||||||
|
const classes = Object.assign({"spinner": true}, extraClasses);
|
||||||
if (container?.classList.contains("legacy")) {
|
if (container?.classList.contains("legacy")) {
|
||||||
return t.div({className: "spinner"}, [
|
return t.div({className: classes}, [
|
||||||
t.div(),
|
t.div(),
|
||||||
t.div(),
|
t.div(),
|
||||||
t.div(),
|
t.div(),
|
||||||
t.div(),
|
t.div(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return t.svg({className: Object.assign({"spinner": true}, extraClasses), viewBox:"0 0 100 100"},
|
return t.svg({className: classes, viewBox:"0 0 100 100"},
|
||||||
t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"})
|
t.circle({cx:"50%", cy:"50%", r:"45%", pathLength:"100"})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1058,3 +1058,20 @@ button.RoomDetailsView_row::after {
|
||||||
.LazyListParent {
|
.LazyListParent {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.LogoutScreen {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LogoutScreen .content {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LogoutScreen .status {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ function objHasFns(obj: ClassNames<unknown>): obj is { [className: string]: bool
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type RenderFn<T> = (t: Builder<T>, vm: T) => ViewNode;
|
export type RenderFn<T> = (t: Builder<T>, vm: T) => ViewNode;
|
||||||
type EventHandler = ((event: Event) => void);
|
type EventHandler = ((event: Event) => void);
|
||||||
type AttributeStaticValue = string | boolean;
|
type AttributeStaticValue = string | boolean;
|
||||||
|
@ -52,20 +51,13 @@ export type Builder<T> = TemplateBuilder<T> & { [tagName in typeof TAG_NAMES[str
|
||||||
- add subviews inside the template
|
- add subviews inside the template
|
||||||
*/
|
*/
|
||||||
// TODO: should we rename this to BoundView or something? As opposed to StaticView ...
|
// TODO: should we rename this to BoundView or something? As opposed to StaticView ...
|
||||||
export class TemplateView<T extends IObservableValue> extends BaseUpdateView<T> {
|
export abstract class TemplateView<T extends IObservableValue> extends BaseUpdateView<T> {
|
||||||
private _render?: RenderFn<T>;
|
|
||||||
private _eventListeners?: { node: Element, name: string, fn: EventHandler, useCapture: boolean }[] = undefined;
|
private _eventListeners?: { node: Element, name: string, fn: EventHandler, useCapture: boolean }[] = undefined;
|
||||||
private _bindings?: (() => void)[] = undefined;
|
private _bindings?: (() => void)[] = undefined;
|
||||||
private _root?: ViewNode = undefined;
|
private _root?: ViewNode = undefined;
|
||||||
// public because used by TemplateBuilder
|
// public because used by TemplateBuilder
|
||||||
_subViews?: IView[] = undefined;
|
_subViews?: IView[] = undefined;
|
||||||
|
|
||||||
constructor(value: T, render?: RenderFn<T>) {
|
|
||||||
super(value);
|
|
||||||
// TODO: can avoid this if we have a separate class for inline templates vs class template views
|
|
||||||
this._render = render;
|
|
||||||
}
|
|
||||||
|
|
||||||
_attach(): void {
|
_attach(): void {
|
||||||
if (this._eventListeners) {
|
if (this._eventListeners) {
|
||||||
for (let {node, name, fn, useCapture} of this._eventListeners) {
|
for (let {node, name, fn, useCapture} of this._eventListeners) {
|
||||||
|
@ -82,16 +74,12 @@ export class TemplateView<T extends IObservableValue> extends BaseUpdateView<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract render(t: Builder<T>, value: T): ViewNode;
|
||||||
|
|
||||||
mount(options?: IMountArgs): ViewNode {
|
mount(options?: IMountArgs): ViewNode {
|
||||||
const builder = new TemplateBuilder(this) as Builder<T>;
|
const builder = new TemplateBuilder(this) as Builder<T>;
|
||||||
try {
|
try {
|
||||||
if (this._render) {
|
this._root = this.render(builder, this._value);
|
||||||
this._root = this._render(builder, this._value);
|
|
||||||
} else if (this["render"]) { // overriden in subclass
|
|
||||||
this._root = this["render"](builder, this._value);
|
|
||||||
} else {
|
|
||||||
throw new Error("no render function passed in, or overriden in subclass");
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
builder.close();
|
builder.close();
|
||||||
}
|
}
|
||||||
|
@ -344,7 +332,7 @@ export class TemplateBuilder<T extends IObservableValue> {
|
||||||
// on mappedValue, use `if` or `mapView`
|
// on mappedValue, use `if` or `mapView`
|
||||||
map<R>(mapFn: (value: T) => R, renderFn: (mapped: R, t: Builder<T>, vm: T) => ViewNode): ViewNode {
|
map<R>(mapFn: (value: T) => R, renderFn: (mapped: R, t: Builder<T>, vm: T) => ViewNode): ViewNode {
|
||||||
return this.mapView(mapFn, mappedValue => {
|
return this.mapView(mapFn, mappedValue => {
|
||||||
return new TemplateView(this._value, (t, vm) => {
|
return new InlineTemplateView(this._value, (t, vm) => {
|
||||||
const rootNode = renderFn(mappedValue, t, vm);
|
const rootNode = renderFn(mappedValue, t, vm);
|
||||||
if (!rootNode) {
|
if (!rootNode) {
|
||||||
// TODO: this will confuse mapView which assumes that
|
// TODO: this will confuse mapView which assumes that
|
||||||
|
@ -366,7 +354,7 @@ export class TemplateBuilder<T extends IObservableValue> {
|
||||||
// creates a conditional subtemplate
|
// creates a conditional subtemplate
|
||||||
// use mapView if you need to map to a different view class
|
// use mapView if you need to map to a different view class
|
||||||
if(predicate: (value: T) => boolean, renderFn: (t: Builder<T>, vm: T) => ViewNode) {
|
if(predicate: (value: T) => boolean, renderFn: (t: Builder<T>, vm: T) => ViewNode) {
|
||||||
return this.ifView(predicate, vm => new TemplateView(vm, renderFn));
|
return this.ifView(predicate, vm => new InlineTemplateView(vm, renderFn));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** You probably are looking for something else, like map or mapView.
|
/** You probably are looking for something else, like map or mapView.
|
||||||
|
@ -398,3 +386,16 @@ for (const [ns, tags] of Object.entries(TAG_NAMES)) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class InlineTemplateView<T> extends TemplateView<T> {
|
||||||
|
private _render: RenderFn<T>;
|
||||||
|
|
||||||
|
constructor(value: T, render: RenderFn<T>) {
|
||||||
|
super(value);
|
||||||
|
this._render = render;
|
||||||
|
}
|
||||||
|
|
||||||
|
override render(t: Builder<T>, value: T): ViewNode {
|
||||||
|
return this._render(t, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {TemplateView} from "../../general/TemplateView";
|
import {TemplateView, InlineTemplateView} from "../../general/TemplateView";
|
||||||
import {StaticView} from "../../general/StaticView.js";
|
import {StaticView} from "../../general/StaticView.js";
|
||||||
|
|
||||||
export class SessionBackupSettingsView extends TemplateView {
|
export class SessionBackupSettingsView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
return t.mapView(vm => vm.status, status => {
|
return t.mapView(vm => vm.status, status => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "Enabled": return new TemplateView(vm, renderEnabled)
|
case "Enabled": return new InlineTemplateView(vm, renderEnabled)
|
||||||
case "SetupKey": return new TemplateView(vm, renderEnableFromKey)
|
case "SetupKey": return new InlineTemplateView(vm, renderEnableFromKey)
|
||||||
case "SetupPhrase": return new TemplateView(vm, renderEnableFromPhrase)
|
case "SetupPhrase": return new InlineTemplateView(vm, renderEnableFromPhrase)
|
||||||
case "Pending": return new StaticView(vm, t => t.p(vm.i18n`Waiting to go online…`))
|
case "Pending": return new StaticView(vm, t => t.p(vm.i18n`Waiting to go online…`))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,11 +42,7 @@ export class SettingsView extends TemplateView {
|
||||||
row(t, vm.i18n`Session ID`, vm.deviceId, "code"),
|
row(t, vm.i18n`Session ID`, vm.deviceId, "code"),
|
||||||
row(t, vm.i18n`Session key`, vm.fingerprintKey, "code"),
|
row(t, vm.i18n`Session key`, vm.fingerprintKey, "code"),
|
||||||
row(t, "", t.button({
|
row(t, "", t.button({
|
||||||
onClick: () => {
|
onClick: () => vm.logout(),
|
||||||
if (confirm(vm.i18n`Are you sure you want to log out?`)) {
|
|
||||||
vm.logout();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
disabled: vm => vm.isLoggingOut
|
disabled: vm => vm.isLoggingOut
|
||||||
}, vm.i18n`Log out`)),
|
}, vm.i18n`Log out`)),
|
||||||
);
|
);
|
||||||
|
|
Reference in a new issue