implement session status bar, with feedback on connection status

This commit is contained in:
Bruno Windels 2020-05-05 23:16:51 +02:00
parent b0e59c30dd
commit 3adc609e07
8 changed files with 145 additions and 72 deletions

View file

@ -33,6 +33,7 @@ export class BrawlViewModel extends ViewModel {
this._setSection(() => { this._setSection(() => {
this._sessionContainer = sessionContainer; this._sessionContainer = sessionContainer;
this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer})); this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer}));
this._sessionViewModel.start();
}); });
} else { } else {
// switch between picker and login // switch between picker and login

View file

@ -0,0 +1,109 @@
import {ViewModel} from "../ViewModel.js";
import {createEnum} from "../../utils/enum.js";
import {ConnectionStatus} from "../../matrix/net/Reconnector.js";
import {SyncStatus} from "../../matrix/Sync.js";
const SessionStatus = createEnum(
"Disconnected",
"Connecting",
"FirstSync",
"Sending",
"Syncing"
);
export class SessionStatusViewModel extends ViewModel {
constructor(options) {
super(options);
const {syncStatus, reconnector} = options;
this._syncStatus = syncStatus;
this._reconnector = reconnector;
this._status = this._calculateState(reconnector.connectionStatus.get(), syncStatus.get());
}
start() {
const update = () => this._updateStatus();
this.track(this._syncStatus.subscribe(update));
this.track(this._reconnector.connectionStatus.subscribe(update));
}
get isShown() {
return this._status !== SessionStatus.Syncing;
}
get statusLabel() {
switch (this._status) {
case SessionStatus.Disconnected:{
const retryIn = Math.round(this._reconnector.retryIn / 1000);
return this.i18n`Disconnected, trying to reconnect in ${retryIn}s…`;
}
case SessionStatus.Connecting:
return this.i18n`Trying to reconnect now…`;
case SessionStatus.FirstSync:
return this.i18n`Catching up with your conversations…`;
}
return "";
}
get isWaiting() {
switch (this._status) {
case SessionStatus.Connecting:
case SessionStatus.FirstSync:
return true;
default:
return false;
}
}
_updateStatus() {
const newStatus = this._calculateState(
this._reconnector.connectionStatus.get(),
this._syncStatus.get()
);
if (newStatus !== this._status) {
if (newStatus === SessionStatus.Disconnected) {
this._retryTimer = this.track(this.clock.createInterval(() => {
this.emitChange("statusLabel");
}, 1000));
} else {
this._retryTimer = this.disposeTracked(this._retryTimer);
}
this._status = newStatus;
console.log("newStatus", newStatus);
this.emitChange();
}
}
_calculateState(connectionStatus, syncStatus) {
if (connectionStatus !== ConnectionStatus.Online) {
switch (connectionStatus) {
case ConnectionStatus.Reconnecting:
return SessionStatus.Connecting;
case ConnectionStatus.Waiting:
return SessionStatus.Disconnected;
}
} else if (syncStatus !== SyncStatus.Syncing) {
switch (syncStatus) {
// InitialSync should be awaited in the SessionLoadViewModel,
// but include it here anyway
case SyncStatus.InitialSync:
case SyncStatus.CatchupSync:
return SessionStatus.FirstSync;
}
} /* else if (session.pendingMessageCount) {
return SessionStatus.Sending;
} */ else {
return SessionStatus.Syncing;
}
}
get isConnectNowShown() {
return this._status === SessionStatus.Disconnected;
}
connectNow() {
if (this.isConnectNowShown) {
this._reconnector.tryNow();
}
}
}

View file

@ -8,7 +8,10 @@ export class SessionViewModel extends ViewModel {
super(options); super(options);
const sessionContainer = options.sessionContainer; const sessionContainer = options.sessionContainer;
this._session = sessionContainer.session; this._session = sessionContainer.session;
this._syncStatusViewModel = new SyncStatusViewModel(this.childOptions(sessionContainer.sync)); this._sessionStatusViewModel = this.track(new SessionStatusViewModel(this.childOptions({
syncStatus: sessionContainer.sync.status,
reconnector: sessionContainer.reconnector
})));
this._currentRoomViewModel = null; this._currentRoomViewModel = null;
const roomTileVMs = this._session.rooms.mapValues((room, emitUpdate) => { const roomTileVMs = this._session.rooms.mapValues((room, emitUpdate) => {
return new RoomTileViewModel({ return new RoomTileViewModel({
@ -20,8 +23,12 @@ export class SessionViewModel extends ViewModel {
this._roomList = roomTileVMs.sortValues((a, b) => a.compare(b)); this._roomList = roomTileVMs.sortValues((a, b) => a.compare(b));
} }
get syncStatusViewModel() { start() {
return this._syncStatusViewModel; this._sessionStatusViewModel.start();
}
get sessionStatusViewModel() {
return this._sessionStatusViewModel;
} }
get roomList() { get roomList() {

View file

@ -1,51 +0,0 @@
import {ViewModel} from "../ViewModel.js";
export class SyncStatusViewModel extends ViewModel {
constructor(sync) {
super();
this._sync = sync;
this._onStatus = this._onStatus.bind(this);
}
_onStatus(status, err) {
if (status === "error") {
this._error = err;
} else if (status === "started") {
this._error = null;
}
this.emitChange();
}
onFirstSubscriptionAdded(name) {
if (name === "change") {
//this._sync.status.("status", this._onStatus);
}
}
onLastSubscriptionRemoved(name) {
if (name === "change") {
//this._sync.status.("status", this._onStatus);
}
}
trySync() {
this._sync.start();
this.emitChange();
}
get status() {
if (!this.isSyncing) {
if (this._error) {
return `Error while syncing: ${this._error.message}`;
} else {
return "Sync stopped";
}
} else {
return "Sync running";
}
}
get isSyncing() {
return this._sync.isSyncing;
}
}

View file

@ -71,7 +71,7 @@ export class SessionContainer {
this._status.set(LoadStatus.Login); this._status.set(LoadStatus.Login);
let sessionInfo; let sessionInfo;
try { try {
const hsApi = new HomeServerApi({homeServer, request: this._request}); const hsApi = new HomeServerApi({homeServer, request: this._request, createTimeout: this._clock.createTimeout});
const loginData = await hsApi.passwordLogin(username, password).response(); const loginData = await hsApi.passwordLogin(username, password).response();
const sessionId = this.createNewSessionId(); const sessionId = this.createNewSessionId();
sessionInfo = { sessionInfo = {
@ -123,6 +123,7 @@ export class SessionContainer {
accessToken: sessionInfo.accessToken, accessToken: sessionInfo.accessToken,
request: this._request, request: this._request,
reconnector: this._reconnector, reconnector: this._reconnector,
createTimeout: this._clock.createTimeout
}); });
this._sessionId = sessionInfo.id; this._sessionId = sessionInfo.id;
this._storage = await this._storageFactory.create(sessionInfo.id); this._storage = await this._storageFactory.create(sessionInfo.id);
@ -203,6 +204,10 @@ export class SessionContainer {
return this._session; return this._session;
} }
get reconnector() {
return this._reconnector;
}
stop() { stop() {
this._reconnectSubscription(); this._reconnectSubscription();
this._reconnectSubscription = null; this._reconnectSubscription = null;

View file

@ -0,0 +1,16 @@
import {TemplateView} from "../general/TemplateView.js";
import {spinner} from "../common.js";
export class SessionStatusView extends TemplateView {
render(t, vm) {
return t.div({className: {
"SessionStatusView": true,
"hidden": vm => !vm.isShown,
}}, [
spinner(t, {hidden: vm => !vm.isWaiting}),
t.p(vm => vm.statusLabel),
t.if(vm => vm.isConnectNowShown, t.createTemplate(t => t.button({onClick: () => vm.connectNow()}, "Retry now"))),
window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : ""
]);
}
}

View file

@ -3,7 +3,7 @@ import {RoomTile} from "./RoomTile.js";
import {RoomView} from "./room/RoomView.js"; import {RoomView} from "./room/RoomView.js";
import {SwitchView} from "../general/SwitchView.js"; import {SwitchView} from "../general/SwitchView.js";
import {RoomPlaceholderView} from "./RoomPlaceholderView.js"; import {RoomPlaceholderView} from "./RoomPlaceholderView.js";
import {SyncStatusBar} from "./SyncStatusBar.js"; import {SessionStatusView} from "./SessionStatusView.js";
import {tag} from "../general/html.js"; import {tag} from "../general/html.js";
export class SessionView { export class SessionView {
@ -22,7 +22,7 @@ export class SessionView {
mount() { mount() {
this._viewModel.on("change", this._onViewModelChange); this._viewModel.on("change", this._onViewModelChange);
this._syncStatusBar = new SyncStatusBar(this._viewModel.syncStatusViewModel); this._sessionStatusBar = new SessionStatusView(this._viewModel.sessionStatusViewModel);
this._roomList = new ListView( this._roomList = new ListView(
{ {
className: "RoomList", className: "RoomList",
@ -34,7 +34,7 @@ export class SessionView {
this._middleSwitcher = new SwitchView(new RoomPlaceholderView()); this._middleSwitcher = new SwitchView(new RoomPlaceholderView());
this._root = tag.div({className: "SessionView"}, [ this._root = tag.div({className: "SessionView"}, [
this._syncStatusBar.mount(), this._sessionStatusBar.mount(),
tag.div({className: "main"}, [ tag.div({className: "main"}, [
tag.div({className: "LeftPanel"}, this._roomList.mount()), tag.div({className: "LeftPanel"}, this._roomList.mount()),
this._middleSwitcher.mount() this._middleSwitcher.mount()

View file

@ -1,14 +0,0 @@
import {TemplateView} from "../general/TemplateView.js";
export class SyncStatusBar extends TemplateView {
render(t, vm) {
return t.div({className: {
"SyncStatusBar": true,
"SyncStatusBar_shown": true,
}}, [
vm => vm.status,
t.if(vm => !vm.isSyncing, t.createTemplate(t => t.button({onClick: () => vm.trySync()}, "Try syncing"))),
window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : ""
]);
}
}