diff --git a/src/domain/session/RoomViewModelObservable.js b/src/domain/session/RoomViewModelObservable.js index fb2fc50200..e11b5c9cb5 100644 --- a/src/domain/session/RoomViewModelObservable.js +++ b/src/domain/session/RoomViewModelObservable.js @@ -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(); diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 5b762c17cb..13e63e2b1d 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,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; } 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 7019277431..dc7ca34002 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -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"; } } diff --git a/src/domain/session/room/WorldReadableRoomViewModel.js b/src/domain/session/room/WorldReadableRoomViewModel.js new file mode 100644 index 0000000000..88ddede7b8 --- /dev/null +++ b/src/domain/session/room/WorldReadableRoomViewModel.js @@ -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); + } + } +} diff --git a/src/matrix/Session.js b/src/matrix/Session.js index a0397567c0..bd5824356f 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 }); } @@ -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; diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index 9f84e872ad..e224fe5225 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -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); } diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js index cd25300d6b..a9ae7b9e61 100644 --- a/src/platform/web/ui/session/room/UnknownRoomView.js +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -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)) + ])); } } 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..092e7ae075 --- /dev/null +++ b/src/platform/web/ui/session/room/WorldReadableRoomView.js @@ -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`) + ]) + ]) + ]); + } +}