diff --git a/.eslintrc.js b/.eslintrc.js
index 2a14eac6..ebc08582 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -12,6 +12,6 @@ module.exports = {
"no-console": "off",
"no-empty": "off",
"no-prototype-builtins": "off",
- "no-unused-vars": "warn",
+ "no-unused-vars": "warn"
}
};
diff --git a/src/domain/navigation/index.js b/src/domain/navigation/index.js
index 3a9b4a07..fab91c11 100644
--- a/src/domain/navigation/index.js
+++ b/src/domain/navigation/index.js
@@ -37,7 +37,7 @@ function allowsChild(parent, child) {
// downside of the approach: both of these will control which tile is selected
return type === "room" || type === "empty-grid-tile";
case "room":
- return type === "lightbox";
+ return type === "lightbox" || type === "details";
default:
return false;
}
@@ -113,6 +113,9 @@ export function parseUrlPath(urlPath, currentNavPath, defaultSessionId) {
segments.push(roomsSegmentWithRoom(rooms, roomId, currentNavPath));
}
segments.push(new Segment("room", roomId));
+ if (currentNavPath.get("details")?.value) {
+ segments.push(new Segment("details"));
+ }
} else if (type === "last-session") {
let sessionSegment = currentNavPath.get("session");
if (typeof sessionSegment?.value !== "string" && defaultSessionId) {
@@ -254,6 +257,25 @@ export function tests() {
assert.equal(segments[2].type, "room");
assert.equal(segments[2].value, "a");
},
+ "parse open-room action changing focus to an existing room with details open": assert => {
+ const nav = new Navigation(allowsChild);
+ const path = nav.pathFrom([
+ new Segment("session", 1),
+ new Segment("rooms", ["a", "b", "c"]),
+ new Segment("room", "b"),
+ new Segment("details", true)
+ ]);
+ const segments = parseUrlPath("/session/1/open-room/a", path);
+ assert.equal(segments.length, 4);
+ assert.equal(segments[0].type, "session");
+ assert.equal(segments[0].value, "1");
+ assert.equal(segments[1].type, "rooms");
+ assert.deepEqual(segments[1].value, ["a", "b", "c"]);
+ assert.equal(segments[2].type, "room");
+ assert.equal(segments[2].value, "a");
+ assert.equal(segments[3].type, "details");
+ assert.equal(segments[3].value, true);
+ },
"parse open-room action setting a room in an empty tile": assert => {
const nav = new Navigation(allowsChild);
const path = nav.pathFrom([
diff --git a/src/domain/session/RoomGridViewModel.js b/src/domain/session/RoomGridViewModel.js
index aee80b6a..dddc603b 100644
--- a/src/domain/session/RoomGridViewModel.js
+++ b/src/domain/session/RoomGridViewModel.js
@@ -78,13 +78,23 @@ export class RoomGridViewModel extends ViewModel {
return this._height;
}
+ _switchToRoom(roomId) {
+ const detailsShown = !!this.navigation.path.get("details")?.value;
+ let path = this.navigation.path.until("rooms");
+ path = path.with(this.navigation.segment("room", roomId));
+ if (detailsShown) {
+ path = path.with(this.navigation.segment("details", true));
+ }
+ this.navigation.applyPath(path);
+ }
+
focusTile(index) {
if (index === this._selectedIndex) {
return;
}
const vmo = this._viewModelsObservables[index];
if (vmo) {
- this.navigation.push("room", vmo.id);
+ this._switchToRoom(vmo.id);
} else {
this.navigation.push("empty-grid-tile", index);
}
@@ -146,7 +156,7 @@ export class RoomGridViewModel extends ViewModel {
this.emitChange();
viewModel?.focus();
}
-
+
/** called from SessionViewModel */
releaseRoomViewModel(roomId) {
const index = this._viewModelsObservables.findIndex(vmo => vmo && vmo.id === roomId);
diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js
index 7a84e67b..087b9315 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 {RoomDetailsViewModel} from "./rightpanel/RoomDetailsViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
@@ -62,6 +63,7 @@ export class SessionViewModel extends ViewModel {
if (!this._gridViewModel) {
this._updateRoom(roomId);
}
+ this._updateRoomDetails();
}));
if (!this._gridViewModel) {
this._updateRoom(currentRoomId.get());
@@ -78,6 +80,10 @@ export class SessionViewModel extends ViewModel {
this._updateLightbox(eventId);
}));
this._updateLightbox(lightbox.get());
+
+ const details = this.navigation.observe("details");
+ this.track(details.subscribe(() => this._updateRoomDetails()));
+ this._updateRoomDetails();
}
get id() {
@@ -112,6 +118,10 @@ export class SessionViewModel extends ViewModel {
return this._roomViewModelObservable?.get();
}
+ get roomDetailsViewModel() {
+ return this._roomDetailsViewModel;
+ }
+
_updateGrid(roomIds) {
const changed = !(this._gridViewModel && roomIds);
const currentRoomId = this.navigation.path.get("room");
@@ -230,8 +240,7 @@ export class SessionViewModel extends ViewModel {
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
}
if (eventId) {
- const roomId = this.navigation.path.get("room").value;
- const room = this._sessionContainer.session.rooms.get(roomId);
+ const room = this._roomFromNavigation();
this._lightboxViewModel = this.track(new LightboxViewModel(this.childOptions({eventId, room})));
}
this.emitChange("lightboxViewModel");
@@ -240,4 +249,22 @@ export class SessionViewModel extends ViewModel {
get lightboxViewModel() {
return this._lightboxViewModel;
}
+
+ _roomFromNavigation() {
+ const roomId = this.navigation.path.get("room")?.value;
+ const room = this._sessionContainer.session.rooms.get(roomId);
+ return room;
+ }
+
+ _updateRoomDetails() {
+ this._roomDetailsViewModel = this.disposeTracked(this._roomDetailsViewModel);
+ const enable = !!this.navigation.path.get("details")?.value;
+ if (enable) {
+ const room = this._roomFromNavigation();
+ if (!room) { return; }
+ this._roomDetailsViewModel = this.track(new RoomDetailsViewModel(this.childOptions({room})));
+ }
+ this.emitChange("roomDetailsViewModel");
+ }
+
}
diff --git a/src/domain/session/leftpanel/LeftPanelViewModel.js b/src/domain/session/leftpanel/LeftPanelViewModel.js
index 6503c124..061c640c 100644
--- a/src/domain/session/leftpanel/LeftPanelViewModel.js
+++ b/src/domain/session/leftpanel/LeftPanelViewModel.js
@@ -92,26 +92,30 @@ export class LeftPanelViewModel extends ViewModel {
}
}
+ _pathForDetails(path) {
+ const details = this.navigation.path.get("details");
+ return details?.value ? path.with(details) : path;
+ }
+
toggleGrid() {
+ const room = this.navigation.path.get("room");
+ let path = this.navigation.path.until("session");
if (this.gridEnabled) {
- let path = this.navigation.path.until("session");
- const room = this.navigation.path.get("room");
if (room) {
path = path.with(room);
+ path = this._pathForDetails(path);
}
- this.navigation.applyPath(path);
} else {
- let path = this.navigation.path.until("session");
- const room = this.navigation.path.get("room");
if (room) {
path = path.with(this.navigation.segment("rooms", [room.value]));
path = path.with(room);
+ path = this._pathForDetails(path);
} else {
path = path.with(this.navigation.segment("rooms", []));
path = path.with(this.navigation.segment("empty-grid-tile", 0));
}
- this.navigation.applyPath(path);
}
+ this.navigation.applyPath(path);
}
get tileViewModels() {
diff --git a/src/domain/session/rightpanel/RoomDetailsViewModel.js b/src/domain/session/rightpanel/RoomDetailsViewModel.js
new file mode 100644
index 00000000..911e5945
--- /dev/null
+++ b/src/domain/session/rightpanel/RoomDetailsViewModel.js
@@ -0,0 +1,61 @@
+import {ViewModel} from "../../ViewModel.js";
+import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
+
+export class RoomDetailsViewModel extends ViewModel {
+ constructor(options) {
+ super(options);
+ this._room = options.room;
+ this._onRoomChange = this._onRoomChange.bind(this);
+ this._room.on("change", this._onRoomChange);
+ }
+
+ get roomId() {
+ return this._room.id;
+ }
+
+ get canonicalAlias() {
+ return this._room.canonicalAlias;
+ }
+
+ get name() {
+ return this._room.name;
+ }
+
+ get isEncrypted() {
+ return !!this._room.isEncrypted;
+ }
+
+ get memberCount() {
+ return this._room.joinedMemberCount;
+ }
+
+ get avatarLetter() {
+ return avatarInitials(this.name);
+ }
+
+ get avatarColorNumber() {
+ return getIdentifierColorNumber(this.roomId)
+ }
+
+ avatarUrl(size) {
+ return getAvatarHttpUrl(this._room.avatarUrl, size, this.platform, this._room.mediaRepository);
+ }
+
+ get avatarTitle() {
+ return this.name;
+ }
+
+ _onRoomChange() {
+ this.emitChange();
+ }
+
+ closePanel() {
+ const path = this.navigation.path.until("room");
+ this.navigation.applyPath(path);
+ }
+
+ dispose() {
+ super.dispose();
+ this._room.off("change", this._onRoomChange);
+ }
+}
diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js
index d2d46c32..4d53ec6c 100644
--- a/src/domain/session/room/RoomViewModel.js
+++ b/src/domain/session/room/RoomViewModel.js
@@ -283,10 +283,16 @@ export class RoomViewModel extends ViewModel {
console.error(err.stack);
}
}
-
+
get composerViewModel() {
return this._composerVM;
}
+
+ openDetailsPanel() {
+ let path = this.navigation.path.until("room");
+ path = path.with(this.navigation.segment("details", true));
+ this.navigation.applyPath(path);
+ }
}
class ComposerViewModel extends ViewModel {
@@ -383,4 +389,4 @@ class ArchivedViewModel extends ViewModel {
get kind() {
return "archived";
}
-}
\ No newline at end of file
+}
diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js
index a1bf7b03..cef443c1 100644
--- a/src/matrix/room/BaseRoom.js
+++ b/src/matrix/room/BaseRoom.js
@@ -362,6 +362,14 @@ export class BaseRoom extends EventEmitter {
return this.membership === "leave";
}
+ get canonicalAlias() {
+ return this._summary.data.canonicalAlias;
+ }
+
+ get joinedMemberCount() {
+ return this._summary.data.joinCount;
+ }
+
get mediaRepository() {
return this._mediaRepository;
}
diff --git a/src/platform/web/ui/css/avatar.css b/src/platform/web/ui/css/avatar.css
index 6e68236f..d369f85f 100644
--- a/src/platform/web/ui/css/avatar.css
+++ b/src/platform/web/ui/css/avatar.css
@@ -53,6 +53,14 @@ limitations under the License.
font-size: calc(var(--avatar-size) * 0.6);
}
+.hydrogen .avatar.size-52 {
+ --avatar-size: 52px;
+ width: var(--avatar-size);
+ height: var(--avatar-size);
+ line-height: var(--avatar-size);
+ font-size: calc(var(--avatar-size) * 0.6);
+}
+
.hydrogen .avatar.size-30 {
--avatar-size: 30px;
width: var(--avatar-size);
diff --git a/src/platform/web/ui/css/layout.css b/src/platform/web/ui/css/layout.css
index 9670afad..fecbbd60 100644
--- a/src/platform/web/ui/css/layout.css
+++ b/src/platform/web/ui/css/layout.css
@@ -54,6 +54,13 @@ main {
min-width: 0;
}
+.right-shown{
+ grid-template:
+ "status status status" auto
+ "left middle right" 1fr /
+ 300px 1fr 300px;
+}
+
/* resize and reposition session view to account for mobile Safari which shifts
the layout viewport up without resizing it when the keyboard shows */
.hydrogen.ios .SessionView {
@@ -65,7 +72,7 @@ the layout viewport up without resizing it when the keyboard shows */
.middle .close-middle { display: none; }
/* mobile layout */
@media screen and (max-width: 800px) {
- .SessionView:not(.middle-shown) {
+ .SessionView:not(.middle-shown):not(.right-shown) {
grid-template:
"status" auto
"left" 1fr /
@@ -79,8 +86,16 @@ the layout viewport up without resizing it when the keyboard shows */
1fr;
}
- .SessionView:not(.middle-shown) .room-placeholder { display: none; }
+ .SessionView.right-shown{
+ grid-template:
+ "status" auto
+ "right" 1fr /
+ 1fr;
+ }
+
+ .SessionView:not(.middle-shown):not(.right-shown) .room-placeholder { display: none; }
.SessionView.middle-shown .LeftPanel { display: none; }
+ .SessionView.right-shown .middle, .SessionView.right-shown .LeftPanel { display: none; }
/* show back button */
.middle .close-middle { display: block !important; }
@@ -179,6 +194,11 @@ the layout viewport up without resizing it when the keyboard shows */
z-index: 2;
}
+.menu .menu-item{
+ box-sizing: border-box;
+ width: 100%;
+}
+
.Settings {
display: flex;
flex-direction: column;
diff --git a/src/platform/web/ui/css/main.css b/src/platform/web/ui/css/main.css
index aa22839e..82b849c8 100644
--- a/src/platform/web/ui/css/main.css
+++ b/src/platform/web/ui/css/main.css
@@ -18,6 +18,7 @@ limitations under the License.
@import url('layout.css');
@import url('login.css');
@import url('left-panel.css');
+@import url('right-panel.css');
@import url('room.css');
@import url('timeline.css');
@import url('avatar.css');
diff --git a/src/platform/web/ui/css/right-panel.css b/src/platform/web/ui/css/right-panel.css
new file mode 100644
index 00000000..f3f34e38
--- /dev/null
+++ b/src/platform/web/ui/css/right-panel.css
@@ -0,0 +1,31 @@
+.RoomDetailsView {
+ grid-area: right;
+ flex-direction: column;
+}
+
+.RoomDetailsView_avatar {
+ display: flex;
+}
+
+.RoomDetailsView_name h2 {
+ text-align: center;
+}
+
+.RoomDetailsView_row {
+ justify-content: space-between;
+}
+
+.RoomDetailsView_label, .RoomDetailsView_row, .RoomDetailsView, .EncryptionIconView {
+ display: flex;
+ align-items: center;
+}
+
+.EncryptionIconView {
+ justify-content: center;
+}
+
+.RoomDetailsView_buttons {
+ display: flex;
+ justify-content: flex-end;
+ width: 100%;
+}
diff --git a/src/platform/web/ui/css/themes/element/icons/e2ee-disabled.svg b/src/platform/web/ui/css/themes/element/icons/e2ee-disabled.svg
new file mode 100644
index 00000000..26e669fc
--- /dev/null
+++ b/src/platform/web/ui/css/themes/element/icons/e2ee-disabled.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/platform/web/ui/css/themes/element/icons/e2ee-normal.svg b/src/platform/web/ui/css/themes/element/icons/e2ee-normal.svg
new file mode 100644
index 00000000..9d981ee7
--- /dev/null
+++ b/src/platform/web/ui/css/themes/element/icons/e2ee-normal.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/platform/web/ui/css/themes/element/icons/encryption-status.svg b/src/platform/web/ui/css/themes/element/icons/encryption-status.svg
new file mode 100644
index 00000000..8c81d4cd
--- /dev/null
+++ b/src/platform/web/ui/css/themes/element/icons/encryption-status.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/platform/web/ui/css/themes/element/icons/info.svg b/src/platform/web/ui/css/themes/element/icons/info.svg
new file mode 100644
index 00000000..d55e9356
--- /dev/null
+++ b/src/platform/web/ui/css/themes/element/icons/info.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/platform/web/ui/css/themes/element/icons/room-members.svg b/src/platform/web/ui/css/themes/element/icons/room-members.svg
new file mode 100644
index 00000000..bc03be13
--- /dev/null
+++ b/src/platform/web/ui/css/themes/element/icons/room-members.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css
index 348f1dcd..885383d7 100644
--- a/src/platform/web/ui/css/themes/element/theme.css
+++ b/src/platform/web/ui/css/themes/element/theme.css
@@ -22,7 +22,6 @@ limitations under the License.
font-size: 10px;
}
-
.hydrogen {
font-family: 'Inter', sans-serif, 'emoji';
background-color: white;
@@ -332,7 +331,6 @@ a {
align-items: center;
}
-
.SessionStatusView button.link {
color: currentcolor;
text-align: left;
@@ -456,6 +454,10 @@ a {
background-image: url("./icons/vertical-ellipsis.svg");
}
+.RoomHeader .room-info {
+ background-image: url("./icons/info.svg");
+}
+
.RoomView_error {
color: red;
}
@@ -660,6 +662,25 @@ button.link {
margin: 0;
}
+.menu li{
+ margin-bottom: 10px;
+}
+
+.menu button {
+ border-radius: 4px;
+ border: none;
+ background-color: transparent;
+ text-align: left;
+ padding: 8px 32px 8px 8px;
+ font-size: 1.5rem;
+ height: 24px;
+ cursor: pointer;
+}
+
+.menu .destructive button {
+ color: #FF4B55;
+}
+
.menu .quick-reactions {
display: flex;
padding: 8px 32px 8px 8px;
@@ -670,20 +691,6 @@ button.link {
text-align: center;
}
-.menu button {
- border-radius: 4px;
- display: block;
- border: none;
- width: 100%;
- background-color: transparent;
- text-align: left;
- padding: 8px 32px 8px 8px;
-}
-
-.menu .destructive button {
- color: #FF4B55;
-}
-
.InviteView_body {
display: flex;
justify-content: space-around;
@@ -779,3 +786,82 @@ button.link {
max-width: 200px;
width: 100%;
}
+
+/* Right Panel */
+
+.RoomDetailsView {
+ background: rgba(245, 245, 245, 0.90);
+ padding: 16px;
+}
+
+.RoomDetailsView_id {
+ color: #737D8C;
+ font-size: 12px;
+}
+
+.RoomDetailsView_rows{
+ margin-top: 36px;
+ width: 100%;
+}
+
+.RoomDetailsView_name h2 {
+ margin-bottom: 4px;
+ font-size: 1.8rem;
+}
+
+.RoomDetailsView_row {
+ margin-bottom: 20px;
+ font-weight: 500;
+ font-size: 15px;
+}
+
+.RoomDetailsView_label::before {
+ padding-right: 16px;
+ height: 24px;
+ width: 20px;
+}
+
+.RoomDetailsView_value {
+ color: #737D8C;
+}
+
+.MemberCount::before {
+ content: url("./icons/room-members.svg");
+}
+
+.EncryptionStatus::before {
+ content: url("./icons/encryption-status.svg");
+}
+
+/* Encryption icon next to avatar */
+
+.EncryptionIconView {
+ width: 52px;
+ height: 52px;
+ border-radius: 100%;
+ background: #737D8C;
+ border: 3px solid #F2F5F8;
+ margin-left: -16px;
+}
+
+.EncryptionIconView_encrypted, .EncryptionIconView_unencrypted {
+ height: 24px;
+ width: 24px;
+}
+
+.EncryptionIconView_encrypted {
+ content: url("./icons/e2ee-normal.svg");
+}
+
+.EncryptionIconView_unencrypted {
+ content: url("./icons/e2ee-disabled.svg");
+}
+
+.RoomDetailsView .button-utility {
+ width: 24px;
+ height: 24px;
+}
+
+.RoomDetailsView .close {
+ background-image: url("./icons/clear.svg");
+}
diff --git a/src/platform/web/ui/general/Menu.js b/src/platform/web/ui/general/Menu.js
index be5dea1d..b846b0e5 100644
--- a/src/platform/web/ui/general/Menu.js
+++ b/src/platform/web/ui/general/Menu.js
@@ -59,6 +59,6 @@ class MenuOption {
}
return t.li({
className,
- }, t.button({onClick: this.callback}, this.label));
+ }, t.button({className:"menu-item", onClick: this.callback}, this.label));
}
}
diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js
index c8a77a1b..877cc67c 100644
--- a/src/platform/web/ui/session/SessionView.js
+++ b/src/platform/web/ui/session/SessionView.js
@@ -25,13 +25,15 @@ import {StaticView} from "../general/StaticView.js";
import {SessionStatusView} from "./SessionStatusView.js";
import {RoomGridView} from "./RoomGridView.js";
import {SettingsView} from "./settings/SettingsView.js";
+import {RoomDetailsView} from "./rightpanel/RoomDetailsView.js";
export class SessionView extends TemplateView {
render(t, vm) {
return t.div({
className: {
"SessionView": true,
- "middle-shown": vm => !!vm.activeMiddleViewModel
+ "middle-shown": vm => !!vm.activeMiddleViewModel,
+ "right-shown": vm => !!vm.roomDetailsViewModel
},
}, [
t.view(new SessionStatusView(vm.sessionStatusViewModel)),
@@ -53,6 +55,7 @@ export class SessionView extends TemplateView {
return new StaticView(t => t.div({className: "room-placeholder"}, t.h2(vm.i18n`Choose a room on the left side.`)));
}
}),
+ t.mapView(vm => vm.roomDetailsViewModel, roomDetailsViewModel => roomDetailsViewModel ? new RoomDetailsView(roomDetailsViewModel) : null),
t.mapView(vm => vm.lightboxViewModel, lightboxViewModel => lightboxViewModel ? new LightboxView(lightboxViewModel) : null)
]);
}
diff --git a/src/platform/web/ui/session/rightpanel/RoomDetailsView.js b/src/platform/web/ui/session/rightpanel/RoomDetailsView.js
new file mode 100644
index 00000000..a6d1a81f
--- /dev/null
+++ b/src/platform/web/ui/session/rightpanel/RoomDetailsView.js
@@ -0,0 +1,51 @@
+import {TemplateView} from "../../general/TemplateView.js";
+import {classNames, tag} from "../../general/html.js";
+import {AvatarView} from "../../avatar.js";
+
+export class RoomDetailsView extends TemplateView {
+ render(t, vm) {
+ const encryptionString = () => vm.isEncrypted ? vm.i18n`On` : vm.i18n`Off`;
+ return t.div({className: "RoomDetailsView"}, [
+ this._createButton(t, vm),
+ t.div({className: "RoomDetailsView_avatar"},
+ [
+ t.view(new AvatarView(vm, 52)),
+ t.mapView(vm => vm.isEncrypted, isEncrypted => new EncryptionIconView(isEncrypted))
+ ]),
+ t.div({className: "RoomDetailsView_name"}, [t.h2(vm => vm.name)]),
+ this._createRoomAliasDisplay(vm),
+ t.div({className: "RoomDetailsView_rows"},
+ [
+ this._createRightPanelRow(t, vm.i18n`People`, {MemberCount: true}, vm => vm.memberCount),
+ this._createRightPanelRow(t, vm.i18n`Encryption`, {EncryptionStatus: true}, encryptionString)
+ ])
+ ]);
+ }
+
+ _createRoomAliasDisplay(vm) {
+ return vm.canonicalAlias ? tag.div({className: "RoomDetailsView_id"}, [vm.canonicalAlias]) :
+ "";
+ }
+
+ _createRightPanelRow(t, label, labelClass, value) {
+ const labelClassString = classNames({RoomDetailsView_label: true, ...labelClass});
+ return t.div({className: "RoomDetailsView_row"}, [
+ t.div({className: labelClassString}, [label]),
+ t.div({className: "RoomDetailsView_value"}, value)
+ ]);
+ }
+
+ _createButton(t, vm) {
+ return t.div({className: "RoomDetailsView_buttons"},
+ [
+ t.button({className: "close button-utility", onClick: () => vm.closePanel()})
+ ]);
+ }
+}
+
+class EncryptionIconView extends TemplateView {
+ render(t, isEncrypted) {
+ return t.div({className: "EncryptionIconView"},
+ [t.div({className: isEncrypted ? "EncryptionIconView_encrypted" : "EncryptionIconView_unencrypted"})]);
+ }
+}
diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js
index 40d7d3c4..f8e84f87 100644
--- a/src/platform/web/ui/session/room/RoomView.js
+++ b/src/platform/web/ui/session/room/RoomView.js
@@ -68,6 +68,7 @@ export class RoomView extends TemplateView {
} else {
const vm = this.value;
const options = [];
+ options.push(Menu.option(vm.i18n`Room details`, () => vm.openDetailsPanel()))
if (vm.canLeave) {
options.push(Menu.option(vm.i18n`Leave room`, () => vm.leaveRoom()).setDestructive());
}