separate view and model for WorldReadableRoom out of UnknownRoom

- UnknownRoomViewModel now receives a promise using which it can display a spinner to the user indicating we are checking whether the preview is possible for this room
and updates the model to WorldReadableRoomViewModel
- kind is now changed to "preview" for WorldReadableRooms
- data stored is now deleted when navigating away to another room by clicking on it
This commit is contained in:
Ashfame 2023-02-25 13:55:20 +04:00
parent 654cdf0317
commit 07cce3857a
9 changed files with 168 additions and 132 deletions

View File

@ -77,10 +77,24 @@ export class RoomViewModelObservable extends ObservableValue {
} else if (status & RoomStatus.Archived) {
return await this._sessionViewModel._createArchivedRoomViewModel(this.id);
} else {
return this._sessionViewModel._createUnknownRoomViewModel(this.id);
return this._sessionViewModel._createUnknownRoomViewModel(this.id, this._isWorldReadablePromise());
}
}
async _isWorldReadablePromise() {
const {session} = this._sessionViewModel._client;
const isWorldReadable = await session.isWorldReadableRoom(this.id);
if (isWorldReadable) {
const vm = await this._sessionViewModel._createWorldReadableRoomViewModel(this.id);
if (vm) {
this.get()?.dispose();
this.set(vm);
return true;
}
}
return false;
}
dispose() {
if (this._statusSubscription) {
this._statusSubscription = this._statusSubscription();

View File

@ -18,6 +18,7 @@ limitations under the License.
import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
import {RoomViewModel} from "./room/RoomViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {WorldReadableRoomViewModel} from "./room/WorldReadableRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {RoomBeingCreatedViewModel} from "./room/RoomBeingCreatedViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
@ -231,12 +232,20 @@ export class SessionViewModel extends ViewModel {
return null;
}
async _createUnknownRoomViewModel(roomIdOrAlias) {
const roomVM = new UnknownRoomViewModel(this.childOptions({
_createUnknownRoomViewModel(roomIdOrAlias, isWorldReadablePromise) {
return new UnknownRoomViewModel(this.childOptions({
roomIdOrAlias,
session: this._client.session,
isWorldReadablePromise: isWorldReadablePromise
}));
void roomVM.load();
}
async _createWorldReadableRoomViewModel(roomIdOrAlias) {
const roomVM = new WorldReadableRoomViewModel(this.childOptions({
room: await this._client.session.loadWorldReadableRoom(roomIdOrAlias),
session: this._client.session,
}));
roomVM.load();
return roomVM;
}

View File

@ -41,7 +41,7 @@ export class RoomViewModel extends ErrorReportViewModel {
this._composerVM = null;
if (room.isArchived) {
this._composerVM = this.track(new ArchivedViewModel(this.childOptions({archivedRoom: room})));
} else {
} else if (!room.isWorldReadable) {
this._recreateComposerOnPowerLevelChange();
}
this._clearUnreadTimout = null;
@ -218,7 +218,7 @@ export class RoomViewModel extends ErrorReportViewModel {
}
}
}
_sendMessage(message, replyingTo) {
return this.logAndCatch("RoomViewModel.sendMessage", async log => {
let success = false;

View File

@ -15,24 +15,21 @@ limitations under the License.
*/
import {ViewModel} from "../../ViewModel";
import {TimelineViewModel} from "./timeline/TimelineViewModel";
import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index";
import {getAvatarHttpUrl} from "../../avatar";
export class UnknownRoomViewModel extends ViewModel {
constructor(options) {
super(options);
const {roomIdOrAlias, session} = options;
const {roomIdOrAlias, session, isWorldReadablePromise} = options;
this._session = session;
this.roomIdOrAlias = roomIdOrAlias;
this._error = null;
this._busy = false;
this._worldReadable = false; // won't know until load() finishes with isWorldReadableRoom() call
this._checkingPreviewCapability = false; // won't know until load() finishes with isWorldReadableRoom() call
}
get room() {
return this._room;
this.checkingPreviewCapability = true;
isWorldReadablePromise.then(() => {
this.checkingPreviewCapability = false;
this.emitChange('checkingPreviewCapability');
})
}
get error() {
@ -61,49 +58,7 @@ export class UnknownRoomViewModel extends ViewModel {
return this._busy;
}
get checkingPreviewCapability() {
return this._checkingPreviewCapability;
}
get kind() {
return this._worldReadable ? "worldReadableRoom" : "unknown";
}
get timelineViewModel() {
return this._timelineVM;
}
avatarUrl(size) {
return getAvatarHttpUrl(this._room.avatarUrl, size, this.platform, this._room.mediaRepository);
}
async load() {
this._checkingPreviewCapability = true;
this._worldReadable = await this._session.isWorldReadableRoom(this.roomIdOrAlias);
this._checkingPreviewCapability = false;
if (!this._worldReadable) {
this.emitChange("checkingPreviewCapability");
return;
}
try {
this._room = await this._session.loadWorldReadableRoom(this.roomIdOrAlias);
const timeline = await this._room.openTimeline();
this._tileOptions = this.childOptions({
roomVM: this,
timeline,
tileClassForEntry: defaultTileClassForEntry,
});
this._timelineVM = this.track(new TimelineViewModel(this.childOptions({
tileOptions: this._tileOptions,
timeline,
})));
this.emitChange("timelineViewModel");
} catch (err) {
console.error(`room.openTimeline(): ${err.message}:\n${err.stack}`);
this._timelineError = err;
this.emitChange("error");
}
return "unknown";
}
}

View File

@ -0,0 +1,47 @@
import {RoomViewModel} from "./RoomViewModel";
export class WorldReadableRoomViewModel extends RoomViewModel {
constructor(options) {
options.room.isWorldReadable = true;
super(options);
this._room = options.room;
this._session = options.session;
this._error = null;
this._busy = false;
}
get kind() {
return "preview";
}
get busy() {
return this._busy;
}
async join() {
this._busy = true;
this.emitChange("busy");
try {
const roomId = await this._session.joinRoom(this._room.id);
// navigate to roomId if we were at the alias
// so we're subscribed to the right room status
// and we'll switch to the room view model once
// the join is synced
this.navigation.push("room", roomId);
// keep busy on true while waiting for the join to sync
} catch (err) {
this._error = err;
this._busy = false;
this.emitChange("error");
}
}
dispose() {
super.dispose();
// if joining the room, _busy would be true and in that case don't delete records
if (!this._busy) {
void this._session.deleteWorldReadableRoomData(this._room.id);
}
}
}

View File

@ -668,7 +668,8 @@ export class Session {
mediaRepository: this._mediaRepository,
pendingEvents: [],
user: this._user,
platform: this._platform
platform: this._platform,
roomStateHandler: this._roomStateHandler
});
}
@ -1106,15 +1107,13 @@ export class Session {
const response = await this._hsApi.messages(roomId, options, {log}).response();
log.set("/messages endpoint response", response);
await this.deleteWorldReadableRoomData(roomId, log);
const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
]);
// clear old records for this room
txn.timelineFragments.removeAllForRoom(roomId);
txn.timelineEvents.removeAllForRoom(roomId);
// insert fragment and event records for this room
const fragment = {
roomId: roomId,
@ -1140,6 +1139,21 @@ export class Session {
});
}
async deleteWorldReadableRoomData(roomId, log = null) {
return this._platform.logger.wrapOrRun(log, "deleteWorldReadableRoomData", async log => {
log.set("id", roomId);
const txn = await this._storage.readWriteTxn([
this._storage.storeNames.timelineFragments,
this._storage.storeNames.timelineEvents,
]);
// clear old records for this room
txn.timelineFragments.removeAllForRoom(roomId);
txn.timelineEvents.removeAllForRoom(roomId);
});
}
joinRoom(roomIdOrAlias, log = null) {
return this._platform.logger.wrapOrRun(log, "joinRoom", async log => {
const body = await this._hsApi.joinIdOrAlias(roomIdOrAlias, {log}).response();
@ -1147,7 +1161,7 @@ export class Session {
});
}
isWorldReadableRoom(roomIdOrAlias, log = null) {
async isWorldReadableRoom(roomIdOrAlias, log = null) {
return this._platform.logger.wrapOrRun(log, "isWorldReadableRoom", async log => {
try {
let roomId;

View File

@ -18,6 +18,7 @@ limitations under the License.
import {LeftPanelView} from "./leftpanel/LeftPanelView.js";
import {RoomView} from "./room/RoomView.js";
import {UnknownRoomView} from "./room/UnknownRoomView.js";
import {WorldReadableRoomView} from "./room/WorldReadableRoomView.js";
import {RoomBeingCreatedView} from "./room/RoomBeingCreatedView.js";
import {InviteView} from "./room/InviteView.js";
import {LightboxView} from "./room/LightboxView.js";
@ -60,6 +61,8 @@ export class SessionView extends TemplateView {
return new RoomView(vm.currentRoomViewModel, viewClassForTile);
} else if (vm.currentRoomViewModel.kind === "roomBeingCreated") {
return new RoomBeingCreatedView(vm.currentRoomViewModel);
} else if (vm.currentRoomViewModel.kind === "preview") {
return new WorldReadableRoomView(vm.currentRoomViewModel);
} else {
return new UnknownRoomView(vm.currentRoomViewModel);
}

View File

@ -14,78 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {InlineTemplateView, TemplateView} from "../../general/TemplateView";
import {AvatarView} from "../../AvatarView";
import {TimelineView} from "./TimelineView";
import {TimelineLoadingView} from "./TimelineLoadingView";
import {TemplateView} from "../../general/TemplateView";
import {spinner} from "../../common.js";
import {viewClassForTile} from "./common";
export class UnknownRoomView extends TemplateView {
constructor(vm) {
super(vm);
}
render(t, vm) {
return t.mapView(vm => vm.kind, kind => {
const unknownRoomView = new InlineTemplateView(vm, (t, m) => {
return t.main({className: "UnknownRoomView middle"}, t.div([
t.h2([
vm.i18n`You are currently not in ${vm.roomIdOrAlias}.`,
t.br(),
vm.i18n`Want to join it?`
]),
t.button({
className: "button-action primary",
onClick: () => vm.join(),
disabled: vm => vm.busy,
}, vm.i18n`Join room`),
t.br(),
t.if(vm => vm.checkingPreviewCapability, t => t.div({className: "checkingPreviewCapability"}, [
spinner(t),
t.p(vm.i18n`Checking preview capability...`)
])),
t.if(vm => vm.error, t => t.p({className: "error"}, vm.error))
]));
});
return kind === 'worldReadableRoom' ? new WorldReadableRoomView(vm) : unknownRoomView;
});
}
}
class WorldReadableRoomView extends InlineTemplateView {
constructor(value, render) {
super(value, render);
}
render(t, vm) {
return t.main({className: "RoomView WorldReadableRoomView middle"}, [
t.div({className: "RoomHeader middle-header"}, [
t.view(new AvatarView(vm, 32)),
t.div({className: "room-description"}, [
t.h2(vm => vm.room.name),
]),
return t.main({className: "UnknownRoomView middle"}, t.div([
t.h2([
vm.i18n`You are currently not in ${vm.roomIdOrAlias}.`,
t.br(),
vm.i18n`Want to join it?`
]),
t.div({className: "RoomView_body"}, [
t.div({className: "RoomView_error"}, [
t.if(vm => vm.error, t => t.div(
[
t.p({}, vm => vm.error),
t.button({className: "RoomView_error_closerButton", onClick: evt => vm.dismissError(evt)})
])
)]),
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
return timelineViewModel ?
new TimelineView(timelineViewModel, viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n
}),
t.div({className: "WorldReadableRoomComposerView"}, [
t.h3(vm => vm.i18n`Join the room to participate`),
t.button({className: "joinRoomButton", onClick: () => vm.join()}, vm.i18n`Join Room`)
])
])
]);
t.button({
className: "button-action primary",
onClick: () => vm.join(),
disabled: vm => vm.busy,
}, vm.i18n`Join room`),
t.br(),
t.if(vm => vm.checkingPreviewCapability, t => t.div({className: "checkingPreviewCapability"}, [
spinner(t),
t.p(vm.i18n`Checking preview capability...`)
])),
t.if(vm => vm.error, t => t.p({className: "error"}, vm.error))
]));
}
}

View File

@ -0,0 +1,44 @@
import {TemplateView} from "../../general/TemplateView";
import {TimelineView} from "./TimelineView";
import {viewClassForTile} from "./common";
import {TimelineLoadingView} from "./TimelineLoadingView";
import {AvatarView} from "../../AvatarView";
export class WorldReadableRoomView extends TemplateView {
constructor(vm) {
super(vm);
}
render(t, vm) {
return t.div({className: "RoomView WorldReadableRoomView middle"}, [
t.div({className: "RoomHeader middle-header"}, [
t.view(new AvatarView(vm, 32)),
t.div({className: "room-description"}, [
t.h2(vm => vm.room.name),
]),
]),
t.div({className: "RoomView_body"}, [
t.div({className: "RoomView_error"}, [
t.if(vm => vm.error, t => t.div([
t.p({}, vm => vm.error),
t.button({className: "RoomView_error_closerButton", onClick: evt => vm.dismissError(evt)})
]))
]),
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
return timelineViewModel ?
new TimelineView(timelineViewModel, viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n
}),
t.div({className: "WorldReadableRoomComposerView"}, [
t.h3(vm => vm.i18n`Join the room to participate`),
t.button({
className: "joinRoomButton",
onClick: () => vm.join(),
disabled: vm => vm.busy,
}, vm.i18n`Join Room`)
])
])
]);
}
}