From 80f0d749c232915d61658a0c7da0e4c3f5d54961 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 17 Feb 2023 14:07:13 +0400 Subject: [PATCH 1/9] delete world readable room data when you move away from that room --- .../session/room/UnknownRoomViewModel.js | 5 +++++ src/matrix/Session.js | 21 +++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index 7019277431..c50498867f 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -106,4 +106,9 @@ export class UnknownRoomViewModel extends ViewModel { this.emitChange("error"); } } + + dispose() { + super.dispose(); + void this._session.deleteWorldReadableRoomData(this.roomIdOrAlias); + } } diff --git a/src/matrix/Session.js b/src/matrix/Session.js index a0397567c0..ce27643c7f 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -1106,15 +1106,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 +1138,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(); From 418fa90191bba782a6b6604d62d474f3b3359065 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 17 Feb 2023 14:22:10 +0400 Subject: [PATCH 2/9] handle edge case when homeserver can not return any events even for a world readable room --- src/domain/session/room/UnknownRoomViewModel.js | 6 ++++++ src/platform/web/ui/session/room/UnknownRoomView.js | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index c50498867f..77ee57111c 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -61,6 +61,12 @@ export class UnknownRoomViewModel extends ViewModel { return this._busy; } + // matrix.org can choose not to return messages for a world_readable room + // so this getter is used to render the correct view, if it's possible to preview the room right now + get previewPossible() { + return this._worldReadable && this._room; + } + get checkingPreviewCapability() { return this._checkingPreviewCapability; } diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js index cd25300d6b..5d389c8195 100644 --- a/src/platform/web/ui/session/room/UnknownRoomView.js +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -49,7 +49,7 @@ export class UnknownRoomView extends TemplateView { t.if(vm => vm.error, t => t.p({className: "error"}, vm.error)) ])); }); - return kind === 'worldReadableRoom' ? new WorldReadableRoomView(vm) : unknownRoomView; + return vm.previewPossible ? new WorldReadableRoomView(vm) : unknownRoomView; }); } } From 554f517950dd9a352ba853b1126a6320037cc7dd Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 17 Feb 2023 20:11:06 +0400 Subject: [PATCH 3/9] possible template fix to fix navigating out of unknown room --- .../web/ui/session/room/UnknownRoomView.js | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js index 5d389c8195..4d8b76d8d4 100644 --- a/src/platform/web/ui/session/room/UnknownRoomView.js +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -27,65 +27,62 @@ export class UnknownRoomView extends TemplateView { 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`), + regularView(vm, t) { + return new InlineTemplateView(vm, (t, m) => { + return t.div([ + t.h2([ + vm.i18n`You are currently not in ${vm.roomIdOrAlias}.`, 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 vm.previewPossible ? new WorldReadableRoomView(vm) : unknownRoomView; + 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)) + ]); }); } -} - -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), + previewView(vm, t) { + return new InlineTemplateView(vm, (t, m) => { + return t.div({className: "RoomView WorldReadableRoomView"}, [ + 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.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.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`) + ]) ]) - ]) - ]); + ]); + }); + } + + render(t, vm) { + return t.main({className: "UnknownRoomView middle"}, t.mapView(vm => vm.kind, kind => { + return vm.previewPossible ? this.previewView(vm, t) : this.regularView(vm, t); + })); } } From e6c4141d2e1a8b6397fdb2fba108dadf6cf8a421 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Wed, 22 Feb 2023 13:12:27 +0400 Subject: [PATCH 4/9] split preview view out of UnknownRoomView into WorldReadableRoomView and re-render the middle for WorldReadableRoomView to take over --- src/domain/session/SessionViewModel.js | 8 +- .../session/room/UnknownRoomViewModel.js | 2 +- src/platform/web/ui/session/SessionView.js | 4 +- .../web/ui/session/room/UnknownRoomView.js | 99 +++++++++---------- 4 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 5b762c17cb..95af9f3b9d 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -236,7 +236,13 @@ export class SessionViewModel extends ViewModel { roomIdOrAlias, session: this._client.session, })); - void roomVM.load(); + roomVM.load().then(() => { + if (roomVM.previewPossible) { + console.log('emitting middle'); + this.emitChange("activeMiddleViewModel"); + this.emitChange(); + } + }); return roomVM; } diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index 77ee57111c..fdb73b6f27 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -64,7 +64,7 @@ export class UnknownRoomViewModel extends ViewModel { // matrix.org can choose not to return messages for a world_readable room // so this getter is used to render the correct view, if it's possible to preview the room right now get previewPossible() { - return this._worldReadable && this._room; + return this._worldReadable && !! this._room; } get checkingPreviewCapability() { diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index 9f84e872ad..2530c80e2e 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -17,7 +17,7 @@ limitations under the License. import {LeftPanelView} from "./leftpanel/LeftPanelView.js"; import {RoomView} from "./room/RoomView.js"; -import {UnknownRoomView} from "./room/UnknownRoomView.js"; +import {UnknownRoomView, WorldReadableRoomView} from "./room/UnknownRoomView.js"; import {RoomBeingCreatedView} from "./room/RoomBeingCreatedView.js"; import {InviteView} from "./room/InviteView.js"; import {LightboxView} from "./room/LightboxView.js"; @@ -60,6 +60,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 === "worldReadableRoom") { + return new WorldReadableRoomView(vm.currentRoomViewModel); } else { return new UnknownRoomView(vm.currentRoomViewModel); } diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js index 4d8b76d8d4..3cd7e0c0fd 100644 --- a/src/platform/web/ui/session/room/UnknownRoomView.js +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -27,62 +27,59 @@ export class UnknownRoomView extends TemplateView { super(vm); } - regularView(vm, t) { - return new InlineTemplateView(vm, (t, m) => { - return 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`), + render(t, vm) { + return t.div({className: "UnknownRoomView middle"}, [ + t.h2([ + vm.i18n`You are currently not in ${vm.roomIdOrAlias}.`, 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)) - ]); - }); + 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)) + ]); } +} - previewView(vm, t) { - return new InlineTemplateView(vm, (t, m) => { - return t.div({className: "RoomView WorldReadableRoomView"}, [ - 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()}, vm.i18n`Join Room`) - ]) - ]) - ]); - }); +export class WorldReadableRoomView extends TemplateView { + + constructor(vm) { + super(vm); } render(t, vm) { - return t.main({className: "UnknownRoomView middle"}, t.mapView(vm => vm.kind, kind => { - return vm.previewPossible ? this.previewView(vm, t) : this.regularView(vm, t); - })); + 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()}, vm.i18n`Join Room`) + ]) + ]) + ]); } } From 7bba357947218b71e145c2709ac817b82667e537 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Thu, 23 Feb 2023 11:53:32 +0400 Subject: [PATCH 5/9] separate WorldReadableRoomViewModel out of UnknownRoomViewModel --- src/domain/session/SessionViewModel.js | 20 +++--- .../session/room/UnknownRoomViewModel.js | 64 +------------------ .../room/WorldReadableRoomViewModel.js | 18 ++++++ src/matrix/Session.js | 3 +- src/platform/web/ui/session/SessionView.js | 2 +- 5 files changed, 33 insertions(+), 74 deletions(-) create mode 100644 src/domain/session/room/WorldReadableRoomViewModel.js diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 95af9f3b9d..9be4b5ab9b 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -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,18 +232,19 @@ export class SessionViewModel extends ViewModel { return null; } - async _createUnknownRoomViewModel(roomIdOrAlias) { - const roomVM = new UnknownRoomViewModel(this.childOptions({ + _createUnknownRoomViewModel(roomIdOrAlias) { + return new UnknownRoomViewModel(this.childOptions({ roomIdOrAlias, session: this._client.session, })); - roomVM.load().then(() => { - if (roomVM.previewPossible) { - console.log('emitting middle'); - this.emitChange("activeMiddleViewModel"); - this.emitChange(); - } - }); + } + + async _createWorldReadableRoomViewModel(roomIdOrAlias) { + const roomVM = new WorldReadableRoomViewModel(this.childOptions({ + room: await this._client.session.loadWorldReadableRoom(roomIdOrAlias), + session: this._client.session, + })); + roomVM.load(); return roomVM; } diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index fdb73b6f27..8bb5fb0af9 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -15,9 +15,6 @@ 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) { @@ -27,12 +24,6 @@ export class UnknownRoomViewModel extends ViewModel { 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; } get error() { @@ -61,60 +52,7 @@ export class UnknownRoomViewModel extends ViewModel { return this._busy; } - // matrix.org can choose not to return messages for a world_readable room - // so this getter is used to render the correct view, if it's possible to preview the room right now - get previewPossible() { - return this._worldReadable && !! this._room; - } - - 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"); - } - } - - dispose() { - super.dispose(); - void this._session.deleteWorldReadableRoomData(this.roomIdOrAlias); + return "unknown"; } } diff --git a/src/domain/session/room/WorldReadableRoomViewModel.js b/src/domain/session/room/WorldReadableRoomViewModel.js new file mode 100644 index 0000000000..a6d781a92b --- /dev/null +++ b/src/domain/session/room/WorldReadableRoomViewModel.js @@ -0,0 +1,18 @@ +import {RoomViewModel} from "./RoomViewModel"; + +export class WorldReadableRoomViewModel extends RoomViewModel { + constructor(options) { + options.room.isWorldReadable = true; + super(options); + this._session = options.session; + } + + get kind() { + return "preview"; + } + + dispose() { + super.dispose(); + void this._session.deleteWorldReadableRoomData(this.roomIdOrAlias); + } +} diff --git a/src/matrix/Session.js b/src/matrix/Session.js index ce27643c7f..87f123f398 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -668,7 +668,8 @@ export class Session { mediaRepository: this._mediaRepository, pendingEvents: [], user: this._user, - platform: this._platform + platform: this._platform, + roomStateHandler: this._roomStateHandler }); } diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index 2530c80e2e..b30deec98e 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -60,7 +60,7 @@ 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 === "worldReadableRoom") { + } else if (vm.currentRoomViewModel.kind === "preview") { return new WorldReadableRoomView(vm.currentRoomViewModel); } else { return new UnknownRoomView(vm.currentRoomViewModel); From 3a700ba677bcfd9afb22b005d10d01e2062e1ec6 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Thu, 23 Feb 2023 17:56:36 +0400 Subject: [PATCH 6/9] move WorldReadableRoomView out to its own file and create WorldReadableRoomViewModel right in the RoomViewModelObservable create a new RoomStatus enum value for WorldReadable and utilise a promise to check whether room is previewable which first UnknownRoomViewModel uses to show UI context and then switches to WorldReadableRoomViewModel to render timeline for preview of messages --- src/domain/session/RoomViewModelObservable.js | 12 ++++- src/domain/session/SessionViewModel.js | 3 +- src/domain/session/room/RoomViewModel.js | 4 +- .../session/room/UnknownRoomViewModel.js | 8 ++- src/matrix/Session.js | 2 +- src/matrix/room/common.ts | 1 + src/platform/web/ui/session/SessionView.js | 3 +- .../web/ui/session/room/UnknownRoomView.js | 50 ++----------------- .../ui/session/room/WorldReadableRoomView.js | 40 +++++++++++++++ 9 files changed, 69 insertions(+), 54 deletions(-) create mode 100644 src/platform/web/ui/session/room/WorldReadableRoomView.js diff --git a/src/domain/session/RoomViewModelObservable.js b/src/domain/session/RoomViewModelObservable.js index fb2fc50200..41ed68bae3 100644 --- a/src/domain/session/RoomViewModelObservable.js +++ b/src/domain/session/RoomViewModelObservable.js @@ -76,8 +76,18 @@ export class RoomViewModelObservable extends ObservableValue { return this._sessionViewModel._createRoomViewModelInstance(this.id); } else if (status & RoomStatus.Archived) { return await this._sessionViewModel._createArchivedRoomViewModel(this.id); + } else if (status & RoomStatus.WorldReadable) { + return await this._sessionViewModel._createWorldReadableRoomViewModel(this.id); } else { - return this._sessionViewModel._createUnknownRoomViewModel(this.id); + const {session} = this._sessionViewModel._client; + const statusObservable = await session.observeRoomStatus(this.id); + const isWorldReadablePromise = session.isWorldReadableRoom(this.id); + isWorldReadablePromise.then(isWorldReadable => { + if (isWorldReadable) { + statusObservable.set(RoomStatus.WorldReadable); + } + }); + return this._sessionViewModel._createUnknownRoomViewModel(this.id, isWorldReadablePromise); } } diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 9be4b5ab9b..13e63e2b1d 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -232,10 +232,11 @@ export class SessionViewModel extends ViewModel { return null; } - _createUnknownRoomViewModel(roomIdOrAlias) { + _createUnknownRoomViewModel(roomIdOrAlias, isWorldReadablePromise) { return new UnknownRoomViewModel(this.childOptions({ roomIdOrAlias, session: this._client.session, + isWorldReadablePromise: isWorldReadablePromise })); } diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 31608a62e7..e35c7bcca0 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -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; diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index 8bb5fb0af9..dc7ca34002 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -19,11 +19,17 @@ import {ViewModel} from "../../ViewModel"; 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.checkingPreviewCapability = true; + isWorldReadablePromise.then(() => { + this.checkingPreviewCapability = false; + this.emitChange('checkingPreviewCapability'); + }) } get error() { diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 87f123f398..bd5824356f 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -1161,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; diff --git a/src/matrix/room/common.ts b/src/matrix/room/common.ts index 2ce8b5dd00..adf5706bac 100644 --- a/src/matrix/room/common.ts +++ b/src/matrix/room/common.ts @@ -35,6 +35,7 @@ export enum RoomStatus { Joined = 1 << 3, Replaced = 1 << 4, Archived = 1 << 5, + WorldReadable = 1 << 6, } export enum RoomType { diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index b30deec98e..e224fe5225 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -17,7 +17,8 @@ limitations under the License. import {LeftPanelView} from "./leftpanel/LeftPanelView.js"; import {RoomView} from "./room/RoomView.js"; -import {UnknownRoomView, WorldReadableRoomView} from "./room/UnknownRoomView.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"; diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js index 3cd7e0c0fd..a9ae7b9e61 100644 --- a/src/platform/web/ui/session/room/UnknownRoomView.js +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -14,21 +14,12 @@ 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.div({className: "UnknownRoomView middle"}, [ + return t.main({className: "UnknownRoomView middle"}, t.div([ t.h2([ vm.i18n`You are currently not in ${vm.roomIdOrAlias}.`, t.br(), @@ -45,41 +36,6 @@ export class UnknownRoomView extends TemplateView { t.p(vm.i18n`Checking preview capability...`) ])), t.if(vm => vm.error, t => t.p({className: "error"}, vm.error)) - ]); - } -} - -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()}, vm.i18n`Join Room`) - ]) - ]) - ]); + ])); } } diff --git a/src/platform/web/ui/session/room/WorldReadableRoomView.js b/src/platform/web/ui/session/room/WorldReadableRoomView.js new file mode 100644 index 0000000000..38ce09d0be --- /dev/null +++ b/src/platform/web/ui/session/room/WorldReadableRoomView.js @@ -0,0 +1,40 @@ +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()}, vm.i18n`Join Room`) + ]) + ]) + ]); + } +} From e9261e53b62007064610ba42165a9515286c3c83 Mon Sep 17 00:00:00 2001 From: Ashfame Date: Thu, 23 Feb 2023 19:44:52 +0400 Subject: [PATCH 7/9] remove the need of defining a new value in RoomStatus enum --- src/domain/session/RoomViewModelObservable.js | 26 +++++++++++-------- src/matrix/room/common.ts | 1 - 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/domain/session/RoomViewModelObservable.js b/src/domain/session/RoomViewModelObservable.js index 41ed68bae3..e11b5c9cb5 100644 --- a/src/domain/session/RoomViewModelObservable.js +++ b/src/domain/session/RoomViewModelObservable.js @@ -76,21 +76,25 @@ export class RoomViewModelObservable extends ObservableValue { return this._sessionViewModel._createRoomViewModelInstance(this.id); } else if (status & RoomStatus.Archived) { return await this._sessionViewModel._createArchivedRoomViewModel(this.id); - } else if (status & RoomStatus.WorldReadable) { - return await this._sessionViewModel._createWorldReadableRoomViewModel(this.id); } else { - const {session} = this._sessionViewModel._client; - const statusObservable = await session.observeRoomStatus(this.id); - const isWorldReadablePromise = session.isWorldReadableRoom(this.id); - isWorldReadablePromise.then(isWorldReadable => { - if (isWorldReadable) { - statusObservable.set(RoomStatus.WorldReadable); - } - }); - return this._sessionViewModel._createUnknownRoomViewModel(this.id, isWorldReadablePromise); + 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(); diff --git a/src/matrix/room/common.ts b/src/matrix/room/common.ts index adf5706bac..2ce8b5dd00 100644 --- a/src/matrix/room/common.ts +++ b/src/matrix/room/common.ts @@ -35,7 +35,6 @@ export enum RoomStatus { Joined = 1 << 3, Replaced = 1 << 4, Archived = 1 << 5, - WorldReadable = 1 << 6, } export enum RoomType { From 8f20f977cc1dada50bff62c906843b06accbd01b Mon Sep 17 00:00:00 2001 From: Ashfame Date: Thu, 23 Feb 2023 23:39:22 +0400 Subject: [PATCH 8/9] make join functional + fix room id being passed to delete during dispose --- .../room/WorldReadableRoomViewModel.js | 27 ++++++++++++++++++- .../ui/session/room/WorldReadableRoomView.js | 6 ++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/domain/session/room/WorldReadableRoomViewModel.js b/src/domain/session/room/WorldReadableRoomViewModel.js index a6d781a92b..7eb6da8e7e 100644 --- a/src/domain/session/room/WorldReadableRoomViewModel.js +++ b/src/domain/session/room/WorldReadableRoomViewModel.js @@ -4,15 +4,40 @@ 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(); - void this._session.deleteWorldReadableRoomData(this.roomIdOrAlias); + void this._session.deleteWorldReadableRoomData(this._room.id); } } diff --git a/src/platform/web/ui/session/room/WorldReadableRoomView.js b/src/platform/web/ui/session/room/WorldReadableRoomView.js index 38ce09d0be..092e7ae075 100644 --- a/src/platform/web/ui/session/room/WorldReadableRoomView.js +++ b/src/platform/web/ui/session/room/WorldReadableRoomView.js @@ -32,7 +32,11 @@ export class WorldReadableRoomView extends TemplateView { }), 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: "joinRoomButton", + onClick: () => vm.join(), + disabled: vm => vm.busy, + }, vm.i18n`Join Room`) ]) ]) ]); From 015f79674bcd6b427d6279648142a3efffcf80ce Mon Sep 17 00:00:00 2001 From: Ashfame Date: Fri, 24 Feb 2023 22:05:13 +0400 Subject: [PATCH 9/9] ensure records are deleted in dispose() except when joining the room --- src/domain/session/room/WorldReadableRoomViewModel.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/domain/session/room/WorldReadableRoomViewModel.js b/src/domain/session/room/WorldReadableRoomViewModel.js index 7eb6da8e7e..88ddede7b8 100644 --- a/src/domain/session/room/WorldReadableRoomViewModel.js +++ b/src/domain/session/room/WorldReadableRoomViewModel.js @@ -38,6 +38,10 @@ export class WorldReadableRoomViewModel extends RoomViewModel { dispose() { super.dispose(); - void this._session.deleteWorldReadableRoomData(this._room.id); + + // 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); + } } }