implement session status bar, with feedback on connection status
This commit is contained in:
parent
b0e59c30dd
commit
3adc609e07
8 changed files with 145 additions and 72 deletions
|
@ -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
|
||||||
|
|
109
src/domain/session/SessionStatusViewModel.js
Normal file
109
src/domain/session/SessionStatusViewModel.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
|
16
src/ui/web/session/SessionStatusView.js
Normal file
16
src/ui/web/session/SessionStatusView.js
Normal 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") : ""
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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") : ""
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue