diff --git a/src/domain/session/RoomViewModelObservable.js b/src/domain/session/RoomViewModelObservable.js index 9252ee81..b9549905 100644 --- a/src/domain/session/RoomViewModelObservable.js +++ b/src/domain/session/RoomViewModelObservable.js @@ -36,6 +36,7 @@ This is also why there is an explicit initialize method, see comment there. export class RoomViewModelObservable extends ObservableValue { constructor(sessionViewModel, roomId) { super(null); + this._statusSubscription = null; this._sessionViewModel = sessionViewModel; this.id = roomId; } @@ -48,9 +49,9 @@ export class RoomViewModelObservable extends ObservableValue { */ async initialize() { const {session} = this._sessionViewModel._sessionContainer; - this._statusObservable = await session.observeRoomStatus(this.id); - this.set(await this._statusToViewModel(this._statusObservable.get())); - this._statusObservable.subscribe(async status => { + const statusObservable = await session.observeRoomStatus(this.id); + this.set(await this._statusToViewModel(statusObservable.get())); + this._statusSubscription = statusObservable.subscribe(async status => { // first dispose existing VM, if any this.get()?.dispose(); this.set(await this._statusToViewModel(status)); @@ -64,8 +65,9 @@ export class RoomViewModelObservable extends ObservableValue { return this._sessionViewModel._createRoomViewModel(this.id); } else if (status.archived) { return await this._sessionViewModel._createArchivedRoomViewModel(this.id); + } else { + return this._sessionViewModel._createUnknownRoomViewModel(this.id); } - return null; } dispose() { diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index ae697c72..6f2c9797 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -17,6 +17,7 @@ limitations under the License. import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js"; import {RoomViewModel} from "./room/RoomViewModel.js"; +import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js"; import {InviteViewModel} from "./room/InviteViewModel.js"; import {LightboxViewModel} from "./room/LightboxViewModel.js"; import {SessionStatusViewModel} from "./SessionStatusViewModel.js"; @@ -162,6 +163,13 @@ export class SessionViewModel extends ViewModel { return null; } + _createUnknownRoomViewModel(roomIdOrAlias) { + return new UnknownRoomViewModel(this.childOptions({ + roomIdOrAlias, + session: this._sessionContainer.session, + })); + } + async _createArchivedRoomViewModel(roomId) { const room = await this._sessionContainer.session.loadArchivedRoom(roomId); if (room) { diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 1d2f3725..5d08f903 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -147,6 +147,14 @@ export class RoomViewModel extends ViewModel { forgetRoom() { this._room.forget(); } + + get canRejoin() { + return this._room.isArchived; + } + + rejoinRoom() { + this._room.join(); + } async _sendMessage(message) { if (!this._room.isArchived && message) { diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js new file mode 100644 index 00000000..e7969298 --- /dev/null +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -0,0 +1,58 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {ViewModel} from "../../ViewModel.js"; + +export class UnknownRoomViewModel extends ViewModel { + constructor(options) { + super(options); + const {roomIdOrAlias, session} = options; + this._session = session; + this.roomIdOrAlias = roomIdOrAlias; + this._error = null; + this._busy = false; + } + + get error() { + return this._error?.message; + } + + async join() { + this._busy = true; + this.emitChange("busy"); + try { + const roomId = await this._session.joinRoom(this.roomIdOrAlias); + // 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"); + } + } + + get busy() { + return this._busy; + } + + get kind() { + return "unknown"; + } +} \ No newline at end of file diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 38771c87..5a046117 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -717,6 +717,13 @@ export class Session { } }); } + + joinRoom(roomIdOrAlias, log = null) { + return this._platform.logger.wrapOrRun(log, "joinRoom", async log => { + const body = await this._hsApi.joinIdOrAlias(roomIdOrAlias, {log}).response(); + return body.room_id; + }); + } } export function tests() { diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js index a3fe1bc9..f834b94b 100644 --- a/src/matrix/net/HomeServerApi.js +++ b/src/matrix/net/HomeServerApi.js @@ -190,6 +190,10 @@ export class HomeServerApi { return this._post(`/rooms/${encodeURIComponent(roomId)}/join`, null, null, options); } + joinIdOrAlias(roomIdOrAlias, options = null) { + return this._post(`/join/${encodeURIComponent(roomIdOrAlias)}`, null, null, options); + } + leave(roomId, options = null) { return this._post(`/rooms/${encodeURIComponent(roomId)}/leave`, null, null, options); } diff --git a/src/matrix/room/ArchivedRoom.js b/src/matrix/room/ArchivedRoom.js index a87bd774..f975191e 100644 --- a/src/matrix/room/ArchivedRoom.js +++ b/src/matrix/room/ArchivedRoom.js @@ -162,6 +162,12 @@ export class ArchivedRoom extends BaseRoom { this._forgetCallback(this.id); }); } + + join(log = null) { + return this._platform.logger.wrapOrRun(log, "rejoin archived room", async log => { + await this._hsApi.join(this.id, {log}).response(); + }); + } } function findKickDetails(roomResponse, ownUserId) { diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 00fdefd0..706bd90d 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -910,4 +910,22 @@ button.link { .RoomArchivedView h3 { margin: 0; +} + +.UnknownRoomView { + align-items: center; + justify-content: center; + text-align: center; + padding: 16px; + box-sizing: border-box; +} + +.UnknownRoomView h2 { + word-break: break-all; + word-break: break-word; +} + +.UnknownRoomView button { + max-width: 200px; + width: 100%; } \ No newline at end of file diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index 214db2a3..c8a77a1b 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -17,6 +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 {InviteView} from "./room/InviteView.js"; import {LightboxView} from "./room/LightboxView.js"; import {TemplateView} from "../general/TemplateView.js"; @@ -43,8 +44,10 @@ export class SessionView extends TemplateView { } else if (vm.currentRoomViewModel) { if (vm.currentRoomViewModel.kind === "invite") { return new InviteView(vm.currentRoomViewModel); - } else { + } else if (vm.currentRoomViewModel.kind === "room") { return new RoomView(vm.currentRoomViewModel); + } else { + return new UnknownRoomView(vm.currentRoomViewModel); } } else { return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`))); diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index e2ef0de7..fff875b8 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -73,6 +73,9 @@ export class RoomView extends TemplateView { if (vm.canForget) { options.push(Menu.option(vm.i18n`Forget room`, () => vm.forgetRoom())); } + if (vm.canRejoin) { + options.push(Menu.option(vm.i18n`Rejoin room`, () => vm.rejoinRoom())); + } if (!options.length) { return; } @@ -86,8 +89,8 @@ export class RoomView extends TemplateView { }, vertical: { relativeTo: "start", - align: "start", - after: 40 + 4 + align: "end", + before: -32 - 4 } }); } diff --git a/src/platform/web/ui/session/room/UnknownRoomView.js b/src/platform/web/ui/session/room/UnknownRoomView.js new file mode 100644 index 00000000..32569d6f --- /dev/null +++ b/src/platform/web/ui/session/room/UnknownRoomView.js @@ -0,0 +1,35 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {TemplateView} from "../../general/TemplateView.js"; + +export class UnknownRoomView extends TemplateView { + render(t, vm) { + 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.if(vm => vm.error, t => t.p({className: "error"}, vm.error)) + ])); + } +}