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 {LoginViewModel} from "./LoginViewModel.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}) {
|
||||
super();
|
||||
this._createSessionContainer = createSessionContainer;
|
||||
|
@ -32,7 +32,7 @@ export class BrawlViewModel extends EventEmitter {
|
|||
if (sessionContainer) {
|
||||
this._setSection(() => {
|
||||
this._sessionContainer = sessionContainer;
|
||||
this._sessionViewModel = new SessionViewModel(sessionContainer);
|
||||
this._sessionViewModel = new SessionViewModel(this.childOptions({sessionContainer}));
|
||||
});
|
||||
} else {
|
||||
// switch between picker and login
|
||||
|
@ -96,7 +96,7 @@ export class BrawlViewModel extends EventEmitter {
|
|||
}
|
||||
// now set it again
|
||||
setter();
|
||||
this.emit("change", "activeSection");
|
||||
this.emitChange("activeSection");
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
export class LoginViewModel extends EventEmitter {
|
||||
export class LoginViewModel extends ViewModel {
|
||||
constructor({sessionCallback, defaultHomeServer, createSessionContainer}) {
|
||||
super();
|
||||
this._createSessionContainer = createSessionContainer;
|
||||
|
@ -10,20 +10,6 @@ export class LoginViewModel extends EventEmitter {
|
|||
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 loadViewModel() {return this._loadViewModel; }
|
||||
|
@ -42,14 +28,14 @@ export class LoginViewModel extends EventEmitter {
|
|||
} else {
|
||||
// show list of session again
|
||||
this._loadViewModel = null;
|
||||
this.emit("change", "loadViewModel");
|
||||
this.emitChange("loadViewModel");
|
||||
}
|
||||
},
|
||||
deleteSessionOnCancel: true,
|
||||
homeserver,
|
||||
});
|
||||
this._loadViewModel.start();
|
||||
this.emit("change", "loadViewModel");
|
||||
this.emitChange("loadViewModel");
|
||||
}
|
||||
|
||||
cancel() {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {EventEmitter} from "../utils/EventEmitter.js";
|
||||
import {LoadStatus, LoginFailure} from "../matrix/SessionContainer.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}) {
|
||||
super();
|
||||
this._createAndStartSessionContainer = createAndStartSessionContainer;
|
||||
|
@ -19,10 +19,10 @@ export class SessionLoadViewModel extends EventEmitter {
|
|||
}
|
||||
try {
|
||||
this._loading = true;
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
this._sessionContainer = this._createAndStartSessionContainer();
|
||||
this._waitHandle = this._sessionContainer.loadStatus.waitFor(s => {
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
// wait for initial sync, but not catchup sync
|
||||
const isCatchupSync = s === LoadStatus.FirstSync &&
|
||||
this._sessionContainer.sync.status === SyncStatus.CatchupSync;
|
||||
|
@ -50,7 +50,7 @@ export class SessionLoadViewModel extends EventEmitter {
|
|||
this._error = err;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ export class SessionLoadViewModel extends EventEmitter {
|
|||
this._sessionCallback();
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {SortedArray} from "../observable/index.js";
|
||||
import {EventEmitter} from "../utils/EventEmitter.js";
|
||||
import {SessionLoadViewModel} from "./SessionLoadViewModel.js";
|
||||
import {ViewModel} from "./ViewModel.js";
|
||||
|
||||
class SessionItemViewModel extends EventEmitter {
|
||||
class SessionItemViewModel extends ViewModel {
|
||||
constructor(sessionInfo, pickerVM) {
|
||||
super();
|
||||
this._pickerVM = pickerVM;
|
||||
|
@ -19,31 +19,31 @@ class SessionItemViewModel extends EventEmitter {
|
|||
|
||||
async delete() {
|
||||
this._isDeleting = true;
|
||||
this.emit("change", "isDeleting");
|
||||
this.emitChange("isDeleting");
|
||||
try {
|
||||
await this._pickerVM.delete(this.id);
|
||||
} catch(err) {
|
||||
this._error = err;
|
||||
console.error(err);
|
||||
this.emit("change", "error");
|
||||
this.emitChange("error");
|
||||
} finally {
|
||||
this._isDeleting = false;
|
||||
this.emit("change", "isDeleting");
|
||||
this.emitChange("isDeleting");
|
||||
}
|
||||
}
|
||||
|
||||
async clear() {
|
||||
this._isClearing = true;
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
try {
|
||||
await this._pickerVM.clear(this.id);
|
||||
} catch(err) {
|
||||
this._error = err;
|
||||
console.error(err);
|
||||
this.emit("change", "error");
|
||||
this.emitChange("error");
|
||||
} finally {
|
||||
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 blob = new Blob([json], {type: "application/json"});
|
||||
this._exportDataUrl = URL.createObjectURL(blob);
|
||||
this.emit("change", "exportDataUrl");
|
||||
this.emitChange("exportDataUrl");
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
console.error(err);
|
||||
|
@ -93,13 +93,13 @@ class SessionItemViewModel extends EventEmitter {
|
|||
if (this._exportDataUrl) {
|
||||
URL.revokeObjectURL(this._exportDataUrl);
|
||||
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}) {
|
||||
super();
|
||||
this._storageFactory = storageFactory;
|
||||
|
@ -141,12 +141,12 @@ export class SessionPickerViewModel extends EventEmitter {
|
|||
} else {
|
||||
// show list of session again
|
||||
this._loadViewModel = null;
|
||||
this.emit("change", "loadViewModel");
|
||||
this.emitChange("loadViewModel");
|
||||
}
|
||||
}
|
||||
});
|
||||
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)
|
||||
// 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) {
|
||||
super();
|
||||
this.disposables = new Disposables();
|
||||
this.disposables = null;
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
|
@ -14,10 +17,33 @@ export class ViewModel extends ObservableValue {
|
|||
}
|
||||
|
||||
track(disposable) {
|
||||
if (!this.disposables) {
|
||||
this.disposables = new Disposables();
|
||||
}
|
||||
this.disposables.track(disposable);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables.dispose();
|
||||
if (this.disposables) {
|
||||
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 {RoomViewModel} from "./room/RoomViewModel.js";
|
||||
import {SyncStatusViewModel} from "./SyncStatusViewModel.js";
|
||||
import {ViewModel} from "../ViewModel.js";
|
||||
|
||||
export class SessionViewModel extends EventEmitter {
|
||||
constructor(sessionContainer) {
|
||||
super();
|
||||
export class SessionViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const sessionContainer = options.sessionContainer;
|
||||
this._session = sessionContainer.session;
|
||||
this._syncStatusViewModel = new SyncStatusViewModel(sessionContainer.sync);
|
||||
this._syncStatusViewModel = new SyncStatusViewModel(this.childOptions(sessionContainer.sync));
|
||||
this._currentRoomViewModel = null;
|
||||
const roomTileVMs = this._session.rooms.mapValues((room, emitUpdate) => {
|
||||
return new RoomTileViewModel({
|
||||
|
@ -42,7 +43,7 @@ export class SessionViewModel extends EventEmitter {
|
|||
if (this._currentRoomViewModel) {
|
||||
this._currentRoomViewModel.dispose();
|
||||
this._currentRoomViewModel = null;
|
||||
this.emit("change", "currentRoom");
|
||||
this.emitChange("currentRoom");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,13 +51,13 @@ export class SessionViewModel extends EventEmitter {
|
|||
if (this._currentRoomViewModel) {
|
||||
this._currentRoomViewModel.dispose();
|
||||
}
|
||||
this._currentRoomViewModel = new RoomViewModel({
|
||||
this._currentRoomViewModel = new RoomViewModel(this.childOptions({
|
||||
room,
|
||||
ownUserId: this._session.user.id,
|
||||
closeCallback: () => this._closeCurrentRoom(),
|
||||
});
|
||||
}));
|
||||
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) {
|
||||
super();
|
||||
this._sync = sync;
|
||||
|
@ -13,7 +13,7 @@ export class SyncStatusViewModel extends EventEmitter {
|
|||
} else if (status === "started") {
|
||||
this._error = null;
|
||||
}
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
onFirstSubscriptionAdded(name) {
|
||||
|
@ -30,7 +30,7 @@ export class SyncStatusViewModel extends EventEmitter {
|
|||
|
||||
trySync() {
|
||||
this._sync.start();
|
||||
this.emit("change");
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
get status() {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import {EventEmitter} from "../../../utils/EventEmitter.js";
|
||||
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
|
||||
import {avatarInitials} from "../avatar.js";
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
|
||||
export class RoomViewModel extends EventEmitter {
|
||||
constructor({room, ownUserId, closeCallback}) {
|
||||
super();
|
||||
export class RoomViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {room, ownUserId, closeCallback} = options;
|
||||
this._room = room;
|
||||
this._ownUserId = ownUserId;
|
||||
this._timeline = null;
|
||||
|
@ -19,12 +20,16 @@ export class RoomViewModel extends EventEmitter {
|
|||
this._room.on("change", this._onRoomChange);
|
||||
try {
|
||||
this._timeline = await this._room.openTimeline();
|
||||
this._timelineVM = new TimelineViewModel(this._room, this._timeline, this._ownUserId);
|
||||
this.emit("change", "timelineViewModel");
|
||||
this._timelineVM = new TimelineViewModel(this.childOptions({
|
||||
room: this._room,
|
||||
timeline: this._timeline,
|
||||
ownUserId: this._ownUserId,
|
||||
}));
|
||||
this.emitChange("timelineViewModel");
|
||||
} catch (err) {
|
||||
console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`);
|
||||
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,
|
||||
// so emit all fields originating from summary
|
||||
_onRoomChange() {
|
||||
this.emit("change", "name");
|
||||
this.emitChange("name");
|
||||
}
|
||||
|
||||
get name() {
|
||||
|
@ -76,7 +81,7 @@ export class RoomViewModel extends EventEmitter {
|
|||
console.error(`room.sendMessage(): ${err.message}:\n${err.stack}`);
|
||||
this._sendError = err;
|
||||
this._timelineError = null;
|
||||
this.emit("change", "error");
|
||||
this.emitChange("error");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -18,7 +18,7 @@ import {TilesCollection} from "./TilesCollection.js";
|
|||
import {tilesCreator} from "./tilesCreator.js";
|
||||
|
||||
export class TimelineViewModel {
|
||||
constructor(room, timeline, ownUserId) {
|
||||
constructor({room, timeline, ownUserId}) {
|
||||
this._timeline = timeline;
|
||||
// once we support sending messages we could do
|
||||
// timeline.entries.concat(timeline.pendingEvents)
|
||||
|
|
Loading…
Reference in a new issue