Skip to content

Commit

Permalink
Merge pull request #870 from vector-im/implement-room-join-ui
Browse files Browse the repository at this point in the history
Implement room join UI
  • Loading branch information
bwindels authored Sep 20, 2022
2 parents 0e40258 + 2e94700 commit 664038b
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/domain/navigation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function allowsChild(parent: Segment<SegmentType> | undefined, child: Segment<Se
// allowed root segments
return type === "login" || type === "session" || type === "sso" || type === "logout";
case "session":
return type === "room" || type === "rooms" || type === "settings" || type === "create-room";
return type === "room" || type === "rooms" || type === "settings" || type === "create-room" || type === "join-room";
case "rooms":
// downside of the approach: both of these will control which tile is selected
return type === "room" || type === "empty-grid-tile";
Expand Down
63 changes: 63 additions & 0 deletions src/domain/session/JoinRoomViewModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2022 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, Options as BaseOptions} from "../ViewModel";
import {SegmentType} from "../navigation/index";
import type {Session} from "../../matrix/Session.js";
import {joinRoom} from "../../matrix/room/joinRoom";

type Options = BaseOptions & {
session: Session;
};

export class JoinRoomViewModel extends ViewModel<SegmentType, Options> {
private _session: Session;
private _joinInProgress: boolean = false;
private _error: Error | undefined;

constructor(options: Readonly<Options>) {
super(options);
this._session = options.session;
}

async join(roomId: string): Promise<void> {
this._error = undefined;
this._joinInProgress = true;
this.emitChange("joinInProgress");
try {
const id = await joinRoom(roomId, this._session);
this.navigation.push("room", id);
}
catch (e) {
this._error = e;
this._joinInProgress = false;
this.emitChange("error");
}
}

get joinInProgress(): boolean {
return this._joinInProgress;
}

get status(): string | undefined {
if (this._error) {
return this._error.message;
}
else if(this._joinInProgress){
return "Joining room";
}
}
}
30 changes: 29 additions & 1 deletion src/domain/session/SessionViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
import {RoomGridViewModel} from "./RoomGridViewModel.js";
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
import {CreateRoomViewModel} from "./CreateRoomViewModel.js";
import {JoinRoomViewModel} from "./JoinRoomViewModel";
import {ViewModel} from "../ViewModel";
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";
Expand All @@ -45,6 +46,7 @@ export class SessionViewModel extends ViewModel {
this._roomViewModelObservable = null;
this._gridViewModel = null;
this._createRoomViewModel = null;
this._joinRoomViewModel = null;
this._setupNavigation();
this._setupForcedLogoutOnAccessTokenInvalidation();
}
Expand Down Expand Up @@ -83,6 +85,12 @@ export class SessionViewModel extends ViewModel {
}));
this._updateCreateRoom(createRoom.get());

const joinRoom = this.navigation.observe("join-room");
this.track(joinRoom.subscribe((joinRoomOpen) => {
this._updateJoinRoom(joinRoomOpen);
}));
this._updateJoinRoom(joinRoom.get());

const lightbox = this.navigation.observe("lightbox");
this.track(lightbox.subscribe(eventId => {
this._updateLightbox(eventId);
Expand Down Expand Up @@ -121,7 +129,13 @@ export class SessionViewModel extends ViewModel {
}

get activeMiddleViewModel() {
return this._roomViewModelObservable?.get() || this._gridViewModel || this._settingsViewModel || this._createRoomViewModel;
return (
this._roomViewModelObservable?.get() ||
this._gridViewModel ||
this._settingsViewModel ||
this._createRoomViewModel ||
this._joinRoomViewModel
);
}

get roomGridViewModel() {
Expand Down Expand Up @@ -152,6 +166,10 @@ export class SessionViewModel extends ViewModel {
return this._createRoomViewModel;
}

get joinRoomViewModel() {
return this._joinRoomViewModel;
}

_updateGrid(roomIds) {
const changed = !(this._gridViewModel && roomIds);
const currentRoomId = this.navigation.path.get("room");
Expand Down Expand Up @@ -286,6 +304,16 @@ export class SessionViewModel extends ViewModel {
this.emitChange("activeMiddleViewModel");
}

_updateJoinRoom(joinRoomOpen) {
if (this._joinRoomViewModel) {
this._joinRoomViewModel = this.disposeTracked(this._joinRoomViewModel);
}
if (joinRoomOpen) {
this._joinRoomViewModel = this.track(new JoinRoomViewModel(this.childOptions({session: this._client.session})));
}
this.emitChange("activeMiddleViewModel");
}

_updateLightbox(eventId) {
if (this._lightboxViewModel) {
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
Expand Down
9 changes: 7 additions & 2 deletions src/domain/session/leftpanel/LeftPanelViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class LeftPanelViewModel extends ViewModel {
this._setupNavigation();
this._closeUrl = this.urlCreator.urlForSegment("session");
this._settingsUrl = this.urlCreator.urlForSegment("settings");
this._createRoomUrl = this.urlCreator.urlForSegment("create-room");
}

_mapTileViewModels(roomsBeingCreated, invites, rooms) {
Expand Down Expand Up @@ -74,8 +73,14 @@ export class LeftPanelViewModel extends ViewModel {
return this._settingsUrl;
}

get createRoomUrl() { return this._createRoomUrl; }
showCreateRoomView() {
this.navigation.push("create-room");
}

showJoinRoomView() {
this.navigation.push("join-room");
}

_setupNavigation() {
const roomObservable = this.navigation.observe("room");
this.track(roomObservable.subscribe(roomId => this._open(roomId)));
Expand Down
19 changes: 4 additions & 15 deletions src/domain/session/room/RoomViewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {imageToInfo} from "../common.js";
// TODO: remove fallback so default isn't included in bundle for SDK users that have their custom tileClassForEntry
// this is a breaking SDK change though to make this option mandatory
import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index";
import {RoomStatus} from "../../../matrix/room/common";
import {joinRoom} from "../../../matrix/room/joinRoom";

export class RoomViewModel extends ViewModel {
constructor(options) {
Expand Down Expand Up @@ -200,22 +200,11 @@ export class RoomViewModel extends ViewModel {

async _processCommandJoin(roomName) {
try {
const roomId = await this._options.client.session.joinRoom(roomName);
const roomStatusObserver = await this._options.client.session.observeRoomStatus(roomId);
await roomStatusObserver.waitFor(status => status === RoomStatus.Joined);
const session = this._options.client.session;
const roomId = await joinRoom(roomName, session);
this.navigation.push("room", roomId);
} catch (err) {
let exc;
if ((err.statusCode ?? err.status) === 400) {
exc = new Error(`/join : '${roomName}' was not legal room ID or room alias`);
} else if ((err.statusCode ?? err.status) === 404 || (err.statusCode ?? err.status) === 502 || err.message == "Internal Server Error") {
exc = new Error(`/join : room '${roomName}' not found`);
} else if ((err.statusCode ?? err.status) === 403) {
exc = new Error(`/join : you're not invited to join '${roomName}'`);
} else {
exc = err;
}
this._sendError = exc;
this._sendError = err;
this._timelineError = null;
this.emitChange("error");
}
Expand Down
42 changes: 42 additions & 0 deletions src/matrix/room/joinRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2022 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 type {Session} from "../Session.js";
import {RoomStatus} from "./common";

/**
* Join a room and wait for it to arrive in the next sync
* @param roomId The id of the room to join
* @param session A session instance
*/
export async function joinRoom(roomId: string, session: Session): Promise<string> {
try {
const internalRoomId = await session.joinRoom(roomId);
const roomStatusObservable = await session.observeRoomStatus(internalRoomId);
await roomStatusObservable.waitFor((status: RoomStatus) => status === RoomStatus.Joined);
return internalRoomId;
}
catch (e) {
if ((e.statusCode ?? e.status) === 400) {
throw new Error(`'${roomId}' is not a legal room ID or alias`);
} else if ((e.statusCode ?? e.status) === 404 || (e.statusCode ?? e.status) === 502 || e.message == "Internal Server eor") {
throw new Error(`Room '${roomId}' could not be found`);
} else if ((e.statusCode ?? e.status) === 403) {
throw new Error(`You are not invited to join '${roomId}'`);
} else {
throw e;
}
}
}
13 changes: 12 additions & 1 deletion src/platform/web/ui/css/themes/element/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ button.RoomDetailsView_row::after {
gap: 12px;
}

.CreateRoomView, .RoomBeingCreated_error {
.CreateRoomView, .JoinRoomView, .RoomBeingCreated_error {
max-width: 400px;
}

Expand Down Expand Up @@ -1211,3 +1211,14 @@ button.RoomDetailsView_row::after {
background-position: center;
background-size: 36px;
}

.JoinRoomView_status {
display: flex;
align-items: center;
justify-content: center;
margin-top: 10px;
}

.JoinRoomView_status .spinner {
margin-right: 5px;
}
63 changes: 63 additions & 0 deletions src/platform/web/ui/session/JoinRoomView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2022 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";
import type {JoinRoomViewModel} from "../../../../domain/session/JoinRoomViewModel";
import {spinner} from "../common.js";

export class JoinRoomView extends TemplateView<JoinRoomViewModel> {
render(t, vm) {
const input = t.input({
type: "text",
name: "id",
id: "id",
placeholder: vm.i18n`Enter a room id or alias`,
disabled: vm => vm.joinInProgress,
});
return t.main({className: "middle"},
t.div({className: "JoinRoomView centered-column"}, [
t.h2("Join room"),
t.form({className: "JoinRoomView_detailsForm form", onSubmit: evt => this.onSubmit(evt, input.value)}, [
t.div({className: "vertical-layout"}, [
t.div({className: "stretch form-row text"}, [
t.label({for: "id"}, vm.i18n`Room id`),
input,
]),
]),
t.div({className: "button-row"}, [
t.button({
className: "button-action primary",
type: "submit",
disabled: vm => vm.joinInProgress
}, vm.i18n`Join`),
]),
t.map(vm => vm.status, (status, t) => {
return t.div({ className: "JoinRoomView_status" }, [
spinner(t, { hidden: vm => !vm.joinInProgress }),
t.span(status),
]);
})
])
])
);
}

onSubmit(evt, id) {
evt.preventDefault();
this.value.join(id);
}
}

3 changes: 3 additions & 0 deletions src/platform/web/ui/session/SessionView.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {SettingsView} from "./settings/SettingsView.js";
import {CreateRoomView} from "./CreateRoomView.js";
import {RightPanelView} from "./rightpanel/RightPanelView.js";
import {viewClassForTile} from "./room/common";
import {JoinRoomView} from "./JoinRoomView";

export class SessionView extends TemplateView {
render(t, vm) {
Expand All @@ -48,6 +49,8 @@ export class SessionView extends TemplateView {
return new SettingsView(vm.settingsViewModel);
} else if (vm.createRoomViewModel) {
return new CreateRoomView(vm.createRoomViewModel);
} else if (vm.joinRoomViewModel) {
return new JoinRoomView(vm.joinRoomViewModel);
} else if (vm.currentRoomViewModel) {
if (vm.currentRoomViewModel.kind === "invite") {
return new InviteView(vm.currentRoomViewModel);
Expand Down
Loading

0 comments on commit 664038b

Please sign in to comment.