forked from mystiq/hydrogen-web
use ViewModel super class for all view models that need binding
This commit is contained in:
parent
d91ab5355c
commit
cc87e35f23
9 changed files with 85 additions and 67 deletions
|
@ -1,9 +1,9 @@
|
||||||
import {SessionViewModel} from "./session/SessionViewModel.js";
|
import {SessionViewModel} from "./session/SessionViewModel.js";
|
||||||
import {LoginViewModel} from "./LoginViewModel.js";
|
import {LoginViewModel} from "./LoginViewModel.js";
|
||||||
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
import {SessionPickerViewModel} from "./SessionPickerViewModel.js";
|
||||||
import {EventEmitter} from "../utils/EventEmitter.js";
|
import {ViewModel} from "./ViewModel.js";
|
||||||
|
|
||||||
export class BrawlViewModel extends EventEmitter {
|
export class BrawlViewModel extends ViewModel {
|
||||||
constructor({createSessionContainer, sessionInfoStorage, storageFactory, clock}) {
|
constructor({createSessionContainer, sessionInfoStorage, storageFactory, clock}) {
|
||||||
super();
|
super();
|
||||||
this._createSessionContainer = createSessionContainer;
|
this._createSessionContainer = createSessionContainer;
|
||||||
|
@ -32,7 +32,7 @@ export class BrawlViewModel extends EventEmitter {
|
||||||
if (sessionContainer) {
|
if (sessionContainer) {
|
||||||
this._setSection(() => {
|
this._setSection(() => {
|
||||||
this._sessionContainer = sessionContainer;
|
this._sessionContainer = sessionContainer;
|
||||||
this._sessionViewModel = new SessionViewModel(sessionContainer);
|
this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// switch between picker and login
|
// switch between picker and login
|
||||||
|
@ -96,7 +96,7 @@ export class BrawlViewModel extends EventEmitter {
|
||||||
}
|
}
|
||||||
// now set it again
|
// now set it again
|
||||||
setter();
|
setter();
|
||||||
this.emit("change", "activeSection");
|
this.emitChange("activeSection");
|
||||||
}
|
}
|
||||||
|
|
||||||
get error() { return this._error; }
|
get error() { return this._error; }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {EventEmitter} from "../utils/EventEmitter.js";
|
import {ViewModel} from "./ViewModel.js";
|
||||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||||
|
|
||||||
export class LoginViewModel extends EventEmitter {
|
export class LoginViewModel extends ViewModel {
|
||||||
constructor({sessionCallback, defaultHomeServer, createSessionContainer}) {
|
constructor({sessionCallback, defaultHomeServer, createSessionContainer}) {
|
||||||
super();
|
super();
|
||||||
this._createSessionContainer = createSessionContainer;
|
this._createSessionContainer = createSessionContainer;
|
||||||
|
@ -10,20 +10,6 @@ export class LoginViewModel extends EventEmitter {
|
||||||
this._loadViewModel = null;
|
this._loadViewModel = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
i18n(parts, ...expr) {
|
|
||||||
// just concat for now
|
|
||||||
let result = "";
|
|
||||||
for (let i = 0; i < parts.length; ++i) {
|
|
||||||
result = result + parts[i];
|
|
||||||
if (i < expr.length) {
|
|
||||||
result = result + expr[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultHomeServer() { return this._defaultHomeServer; }
|
get defaultHomeServer() { return this._defaultHomeServer; }
|
||||||
|
|
||||||
get loadViewModel() {return this._loadViewModel; }
|
get loadViewModel() {return this._loadViewModel; }
|
||||||
|
@ -42,14 +28,14 @@ export class LoginViewModel extends EventEmitter {
|
||||||
} else {
|
} else {
|
||||||
// show list of session again
|
// show list of session again
|
||||||
this._loadViewModel = null;
|
this._loadViewModel = null;
|
||||||
this.emit("change", "loadViewModel");
|
this.emitChange("loadViewModel");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteSessionOnCancel: true,
|
deleteSessionOnCancel: true,
|
||||||
homeserver,
|
homeserver,
|
||||||
});
|
});
|
||||||
this._loadViewModel.start();
|
this._loadViewModel.start();
|
||||||
this.emit("change", "loadViewModel");
|
this.emitChange("loadViewModel");
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {EventEmitter} from "../utils/EventEmitter.js";
|
|
||||||
import {LoadStatus, LoginFailure} from "../matrix/SessionContainer.js";
|
import {LoadStatus, LoginFailure} from "../matrix/SessionContainer.js";
|
||||||
import {SyncStatus} from "../matrix/Sync.js";
|
import {SyncStatus} from "../matrix/Sync.js";
|
||||||
|
import {ViewModel} from "./ViewModel.js";
|
||||||
|
|
||||||
export class SessionLoadViewModel extends EventEmitter {
|
export class SessionLoadViewModel extends ViewModel {
|
||||||
constructor({createAndStartSessionContainer, sessionCallback, homeserver, deleteSessionOnCancel}) {
|
constructor({createAndStartSessionContainer, sessionCallback, homeserver, deleteSessionOnCancel}) {
|
||||||
super();
|
super();
|
||||||
this._createAndStartSessionContainer = createAndStartSessionContainer;
|
this._createAndStartSessionContainer = createAndStartSessionContainer;
|
||||||
|
@ -19,10 +19,10 @@ export class SessionLoadViewModel extends EventEmitter {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this._loading = true;
|
this._loading = true;
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
this._sessionContainer = this._createAndStartSessionContainer();
|
this._sessionContainer = this._createAndStartSessionContainer();
|
||||||
this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => {
|
this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => {
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
// wait for initial sync, but not catchup sync
|
// wait for initial sync, but not catchup sync
|
||||||
const isCatchupSync = s === LoadStatus.FirstSync &&
|
const isCatchupSync = s === LoadStatus.FirstSync &&
|
||||||
this._sessionContainer.sync.status === SyncStatus.CatchupSync;
|
this._sessionContainer.sync.status === SyncStatus.CatchupSync;
|
||||||
|
@ -50,7 +50,7 @@ export class SessionLoadViewModel extends EventEmitter {
|
||||||
this._error = err;
|
this._error = err;
|
||||||
} finally {
|
} finally {
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ export class SessionLoadViewModel extends EventEmitter {
|
||||||
this._sessionCallback();
|
this._sessionCallback();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._error = err;
|
this._error = err;
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {SortedArray} from "../observable/index.js";
|
import {SortedArray} from "../observable/index.js";
|
||||||
import {EventEmitter} from "../utils/EventEmitter.js";
|
|
||||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||||
|
import {ViewModel} from "./ViewModel.js";
|
||||||
|
|
||||||
class SessionItemViewModel extends EventEmitter {
|
class SessionItemViewModel extends ViewModel {
|
||||||
constructor(sessionInfo, pickerVM) {
|
constructor(sessionInfo, pickerVM) {
|
||||||
super();
|
super();
|
||||||
this._pickerVM = pickerVM;
|
this._pickerVM = pickerVM;
|
||||||
|
@ -19,31 +19,31 @@ class SessionItemViewModel extends EventEmitter {
|
||||||
|
|
||||||
async delete() {
|
async delete() {
|
||||||
this._isDeleting = true;
|
this._isDeleting = true;
|
||||||
this.emit("change", "isDeleting");
|
this.emitChange("isDeleting");
|
||||||
try {
|
try {
|
||||||
await this._pickerVM.delete(this.id);
|
await this._pickerVM.delete(this.id);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this._error = err;
|
this._error = err;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.emit("change", "error");
|
this.emitChange("error");
|
||||||
} finally {
|
} finally {
|
||||||
this._isDeleting = false;
|
this._isDeleting = false;
|
||||||
this.emit("change", "isDeleting");
|
this.emitChange("isDeleting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear() {
|
async clear() {
|
||||||
this._isClearing = true;
|
this._isClearing = true;
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
try {
|
try {
|
||||||
await this._pickerVM.clear(this.id);
|
await this._pickerVM.clear(this.id);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this._error = err;
|
this._error = err;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.emit("change", "error");
|
this.emitChange("error");
|
||||||
} finally {
|
} finally {
|
||||||
this._isClearing = false;
|
this._isClearing = false;
|
||||||
this.emit("change", "isClearing");
|
this.emitChange("isClearing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ class SessionItemViewModel extends EventEmitter {
|
||||||
const json = JSON.stringify(data, undefined, 2);
|
const json = JSON.stringify(data, undefined, 2);
|
||||||
const blob = new Blob([json], {type: "application/json"});
|
const blob = new Blob([json], {type: "application/json"});
|
||||||
this._exportDataUrl = URL.createObjectURL(blob);
|
this._exportDataUrl = URL.createObjectURL(blob);
|
||||||
this.emit("change", "exportDataUrl");
|
this.emitChange("exportDataUrl");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -93,13 +93,13 @@ class SessionItemViewModel extends EventEmitter {
|
||||||
if (this._exportDataUrl) {
|
if (this._exportDataUrl) {
|
||||||
URL.revokeObjectURL(this._exportDataUrl);
|
URL.revokeObjectURL(this._exportDataUrl);
|
||||||
this._exportDataUrl = null;
|
this._exportDataUrl = null;
|
||||||
this.emit("change", "exportDataUrl");
|
this.emitChange("exportDataUrl");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class SessionPickerViewModel extends EventEmitter {
|
export class SessionPickerViewModel extends ViewModel {
|
||||||
constructor({storageFactory, sessionInfoStorage, sessionCallback, createSessionContainer}) {
|
constructor({storageFactory, sessionInfoStorage, sessionCallback, createSessionContainer}) {
|
||||||
super();
|
super();
|
||||||
this._storageFactory = storageFactory;
|
this._storageFactory = storageFactory;
|
||||||
|
@ -141,12 +141,12 @@ export class SessionPickerViewModel extends EventEmitter {
|
||||||
} else {
|
} else {
|
||||||
// show list of session again
|
// show list of session again
|
||||||
this._loadViewModel = null;
|
this._loadViewModel = null;
|
||||||
this.emit("change", "loadViewModel");
|
this.emitChange("loadViewModel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._loadViewModel.start();
|
this._loadViewModel.start();
|
||||||
this.emit("change", "loadViewModel");
|
this.emitChange("loadViewModel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
// as in some cases it would really be more convenient to have multiple events (like telling the timeline to scroll down)
|
// 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
|
// 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
|
||||||
|
|
||||||
export class ViewModel extends ObservableValue {
|
import {EventEmitter} from "../utils/EventEmitter.js";
|
||||||
|
import {Disposables} from "../utils/Disposables.js";
|
||||||
|
|
||||||
|
export class ViewModel extends EventEmitter {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super();
|
super();
|
||||||
this.disposables = new Disposables();
|
this.disposables = null;
|
||||||
this._options = options;
|
this._options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +17,33 @@ export class ViewModel extends ObservableValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
track(disposable) {
|
track(disposable) {
|
||||||
|
if (!this.disposables) {
|
||||||
|
this.disposables = new Disposables();
|
||||||
|
}
|
||||||
this.disposables.track(disposable);
|
this.disposables.track(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
if (this.disposables) {
|
||||||
this.disposables.dispose();
|
this.disposables.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
i18n(parts, ...expr) {
|
||||||
|
// just concat for now
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < parts.length; ++i) {
|
||||||
|
result = result + parts[i];
|
||||||
|
if (i < expr.length) {
|
||||||
|
result = result + expr[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitChange(changedProps) {
|
||||||
|
this.emit("change", changedProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import {EventEmitter} from "../../utils/EventEmitter.js";
|
|
||||||
import {RoomTileViewModel} from "./roomlist/RoomTileViewModel.js";
|
import {RoomTileViewModel} from "./roomlist/RoomTileViewModel.js";
|
||||||
import {RoomViewModel} from "./room/RoomViewModel.js";
|
import {RoomViewModel} from "./room/RoomViewModel.js";
|
||||||
import {SyncStatusViewModel} from "./SyncStatusViewModel.js";
|
import {SyncStatusViewModel} from "./SyncStatusViewModel.js";
|
||||||
|
import {ViewModel} from "../ViewModel.js";
|
||||||
|
|
||||||
export class SessionViewModel extends EventEmitter {
|
export class SessionViewModel extends ViewModel {
|
||||||
constructor(sessionContainer) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
|
const sessionContainer = options.sessionContainer;
|
||||||
this._session = sessionContainer.session;
|
this._session = sessionContainer.session;
|
||||||
this._syncStatusViewModel = new SyncStatusViewModel(sessionContainer.sync);
|
this._syncStatusViewModel = new SyncStatusViewModel(this.childOptions(sessionContainer.sync));
|
||||||
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({
|
||||||
|
@ -42,7 +43,7 @@ export class SessionViewModel extends EventEmitter {
|
||||||
if (this._currentRoomViewModel) {
|
if (this._currentRoomViewModel) {
|
||||||
this._currentRoomViewModel.dispose();
|
this._currentRoomViewModel.dispose();
|
||||||
this._currentRoomViewModel = null;
|
this._currentRoomViewModel = null;
|
||||||
this.emit("change", "currentRoom");
|
this.emitChange("currentRoom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +51,13 @@ export class SessionViewModel extends EventEmitter {
|
||||||
if (this._currentRoomViewModel) {
|
if (this._currentRoomViewModel) {
|
||||||
this._currentRoomViewModel.dispose();
|
this._currentRoomViewModel.dispose();
|
||||||
}
|
}
|
||||||
this._currentRoomViewModel = new RoomViewModel({
|
this._currentRoomViewModel = new RoomViewModel(this.childOptions({
|
||||||
room,
|
room,
|
||||||
ownUserId: this._session.user.id,
|
ownUserId: this._session.user.id,
|
||||||
closeCallback: () => this._closeCurrentRoom(),
|
closeCallback: () => this._closeCurrentRoom(),
|
||||||
});
|
}));
|
||||||
this._currentRoomViewModel.load();
|
this._currentRoomViewModel.load();
|
||||||
this.emit("change", "currentRoom");
|
this.emitChange("currentRoom");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {EventEmitter} from "../../utils/EventEmitter.js";
|
import {ViewModel} from "../ViewModel.js";
|
||||||
|
|
||||||
export class SyncStatusViewModel extends EventEmitter {
|
export class SyncStatusViewModel extends ViewModel {
|
||||||
constructor(sync) {
|
constructor(sync) {
|
||||||
super();
|
super();
|
||||||
this._sync = sync;
|
this._sync = sync;
|
||||||
|
@ -13,7 +13,7 @@ export class SyncStatusViewModel extends EventEmitter {
|
||||||
} else if (status === "started") {
|
} else if (status === "started") {
|
||||||
this._error = null;
|
this._error = null;
|
||||||
}
|
}
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFirstSubscriptionAdded(name) {
|
onFirstSubscriptionAdded(name) {
|
||||||
|
@ -30,7 +30,7 @@ export class SyncStatusViewModel extends EventEmitter {
|
||||||
|
|
||||||
trySync() {
|
trySync() {
|
||||||
this._sync.start();
|
this._sync.start();
|
||||||
this.emit("change");
|
this.emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
get status() {
|
get status() {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import {EventEmitter} from "../../../utils/EventEmitter.js";
|
|
||||||
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
||||||
import {avatarInitials} from "../avatar.js";
|
import {avatarInitials} from "../avatar.js";
|
||||||
|
import {ViewModel} from "../../ViewModel.js";
|
||||||
|
|
||||||
export class RoomViewModel extends EventEmitter {
|
export class RoomViewModel extends ViewModel {
|
||||||
constructor({room, ownUserId, closeCallback}) {
|
constructor(options) {
|
||||||
super();
|
super(options);
|
||||||
|
const {room, ownUserId, closeCallback} = options;
|
||||||
this._room = room;
|
this._room = room;
|
||||||
this._ownUserId = ownUserId;
|
this._ownUserId = ownUserId;
|
||||||
this._timeline = null;
|
this._timeline = null;
|
||||||
|
@ -19,12 +20,16 @@ export class RoomViewModel extends EventEmitter {
|
||||||
this._room.on("change", this._onRoomChange);
|
this._room.on("change", this._onRoomChange);
|
||||||
try {
|
try {
|
||||||
this._timeline = await this._room.openTimeline();
|
this._timeline = await this._room.openTimeline();
|
||||||
this._timelineVM = new TimelineViewModel(this._room, this._timeline, this._ownUserId);
|
this._timelineVM = new TimelineViewModel(this.childOptions({
|
||||||
this.emit("change", "timelineViewModel");
|
room: this._room,
|
||||||
|
timeline: this._timeline,
|
||||||
|
ownUserId: this._ownUserId,
|
||||||
|
}));
|
||||||
|
this.emitChange("timelineViewModel");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`);
|
console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`);
|
||||||
this._timelineError = err;
|
this._timelineError = err;
|
||||||
this.emit("change", "error");
|
this.emitChange("error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +48,7 @@ export class RoomViewModel extends EventEmitter {
|
||||||
// room doesn't tell us yet which fields changed,
|
// room doesn't tell us yet which fields changed,
|
||||||
// so emit all fields originating from summary
|
// so emit all fields originating from summary
|
||||||
_onRoomChange() {
|
_onRoomChange() {
|
||||||
this.emit("change", "name");
|
this.emitChange("name");
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
|
@ -76,7 +81,7 @@ export class RoomViewModel extends EventEmitter {
|
||||||
console.error(`room.sendMessage(): ${err.message}:\n${err.stack}`);
|
console.error(`room.sendMessage(): ${err.message}:\n${err.stack}`);
|
||||||
this._sendError = err;
|
this._sendError = err;
|
||||||
this._timelineError = null;
|
this._timelineError = null;
|
||||||
this.emit("change", "error");
|
this.emitChange("error");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {TilesCollection} from "./TilesCollection.js";
|
||||||
import {tilesCreator} from "./tilesCreator.js";
|
import {tilesCreator} from "./tilesCreator.js";
|
||||||
|
|
||||||
export class TimelineViewModel {
|
export class TimelineViewModel {
|
||||||
constructor(room, timeline, ownUserId) {
|
constructor({room, timeline, ownUserId}) {
|
||||||
this._timeline = timeline;
|
this._timeline = timeline;
|
||||||
// once we support sending messages we could do
|
// once we support sending messages we could do
|
||||||
// timeline.entries.concat(timeline.pendingEvents)
|
// timeline.entries.concat(timeline.pendingEvents)
|
||||||
|
|
Loading…
Reference in a new issue