Merge branch 'develop' into travis/badge-ts
This commit is contained in:
commit
33e4ecbe86
29 changed files with 989 additions and 185 deletions
|
@ -12,6 +12,7 @@
|
||||||
@import "./structures/_HeaderButtons.scss";
|
@import "./structures/_HeaderButtons.scss";
|
||||||
@import "./structures/_HomePage.scss";
|
@import "./structures/_HomePage.scss";
|
||||||
@import "./structures/_LeftPanel.scss";
|
@import "./structures/_LeftPanel.scss";
|
||||||
|
@import "./structures/_LeftPanel2.scss";
|
||||||
@import "./structures/_MainSplit.scss";
|
@import "./structures/_MainSplit.scss";
|
||||||
@import "./structures/_MatrixChat.scss";
|
@import "./structures/_MatrixChat.scss";
|
||||||
@import "./structures/_MyGroups.scss";
|
@import "./structures/_MyGroups.scss";
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
@import "./structures/_ToastContainer.scss";
|
@import "./structures/_ToastContainer.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
@import "./structures/_TopLeftMenuButton.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
|
@import "./structures/_UserMenuButton.scss";
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
@import "./structures/auth/_CompleteSecurity.scss";
|
@import "./structures/auth/_CompleteSecurity.scss";
|
||||||
@import "./structures/auth/_Login.scss";
|
@import "./structures/auth/_Login.scss";
|
||||||
|
@ -177,10 +179,12 @@
|
||||||
@import "./views/rooms/_RoomDropTarget.scss";
|
@import "./views/rooms/_RoomDropTarget.scss";
|
||||||
@import "./views/rooms/_RoomHeader.scss";
|
@import "./views/rooms/_RoomHeader.scss";
|
||||||
@import "./views/rooms/_RoomList.scss";
|
@import "./views/rooms/_RoomList.scss";
|
||||||
|
@import "./views/rooms/_RoomList2.scss";
|
||||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
||||||
@import "./views/rooms/_RoomSublist2.scss";
|
@import "./views/rooms/_RoomSublist2.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
|
@import "./views/rooms/_RoomTile2.scss";
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
@import "./views/rooms/_SearchBar.scss";
|
@import "./views/rooms/_SearchBar.scss";
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
|
|
|
@ -23,14 +23,6 @@ limitations under the License.
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove temporary indicator of new room list implementation.
|
|
||||||
// This border is meant to visually distinguish between the two components when the
|
|
||||||
// user has turned on the new room list implementation, at least until the designs
|
|
||||||
// themselves give it away.
|
|
||||||
.mx_LeftPanel2 .mx_LeftPanel {
|
|
||||||
border-left: 5px #e26dff solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_LeftPanel_container.collapsed {
|
.mx_LeftPanel_container.collapsed {
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
/* Collapsed LeftPanel 50px */
|
/* Collapsed LeftPanel 50px */
|
||||||
|
|
99
res/css/structures/_LeftPanel2.scss
Normal file
99
res/css/structures/_LeftPanel2.scss
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Rename to mx_LeftPanel during replacement of old component
|
||||||
|
|
||||||
|
// TODO: Put these variables in the right place, or namespace them.
|
||||||
|
$tagPanelWidth: 70px;
|
||||||
|
$roomListMinimizedWidth: 50px;
|
||||||
|
|
||||||
|
.mx_LeftPanel2 {
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 50%;
|
||||||
|
|
||||||
|
// Create a row-based flexbox for the TagPanel and the room list
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.mx_LeftPanel2_tagPanelContainer {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-basis: $tagPanelWidth;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// Create another flexbox so the TagPanel fills the container
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
// TagPanel handles its own CSS
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The 'room list' in this context is actually everything that isn't the tag
|
||||||
|
// panel, such as the menu options, breadcrumbs, filtering, etc
|
||||||
|
.mx_LeftPanel2_roomListContainer {
|
||||||
|
width: calc(100% - $tagPanelWidth);
|
||||||
|
|
||||||
|
// Create another flexbox (this time a column) for the room list components
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.mx_LeftPanel2_userHeader {
|
||||||
|
padding: 14px 12px 20px; // 14px top, 12px sides, 20px bottom
|
||||||
|
|
||||||
|
// Create another flexbox column for the rows to stack within
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
// There's 2 rows when breadcrumbs are present: the top bit and the breadcrumbs
|
||||||
|
.mx_LeftPanel2_headerRow {
|
||||||
|
// Create yet another flexbox, this time within the row, to ensure items stay
|
||||||
|
// aligned correctly. This is also a row-based flexbox.
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_userAvatarContainer {
|
||||||
|
position: relative; // to make default avatars work
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_userName {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_headerButtons {
|
||||||
|
// No special styles: the rest of the layout happens to make it work.
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_breadcrumbsContainer {
|
||||||
|
// TODO: Improve CSS for breadcrumbs (currently shoved into the view rather than placed)
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_filterContainer {
|
||||||
|
// TODO: Improve CSS for filtering and its input
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel2_actualRoomListContainer {
|
||||||
|
flex-grow: 1; // fill the available space
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
/* not the left panel, and not the resize handle, so the roomview/groupview/... */
|
/* not the left panel, and not the resize handle, so the roomview/groupview/... */
|
||||||
.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_ResizeHandle) {
|
.mx_MatrixChat > :not(.mx_LeftPanel_container):not(.mx_LeftPanel2):not(.mx_ResizeHandle) {
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
|
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
|
162
res/css/structures/_UserMenuButton.scss
Normal file
162
res/css/structures/_UserMenuButton.scss
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_UserMenuButton {
|
||||||
|
// No special styles on the button itself
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu {
|
||||||
|
width: 231px;
|
||||||
|
|
||||||
|
// Put 20px of padding around the whole menu. We do this instead of a
|
||||||
|
// simple `padding: 20px` rule so the horizontal rules added by the
|
||||||
|
// optionLists is rendered correctly (full width).
|
||||||
|
> * {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_header {
|
||||||
|
// Create a flexbox to organize the header a bit easier
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:nth-child(n + 1) {
|
||||||
|
// The first header will have appropriate padding, subsequent ones need a margin.
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_name {
|
||||||
|
// Create another flexbox of columns to handle large user IDs
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
// fit the container
|
||||||
|
flex: 1;
|
||||||
|
width: calc(100% - 40px); // 40px = 32px theme button + 8px margin to theme button
|
||||||
|
|
||||||
|
* {
|
||||||
|
// Automatically grow all subelements to fit the container
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
// Ellipsize any text overflow
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_displayName {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_userId {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_themeButton {
|
||||||
|
min-width: 32px;
|
||||||
|
max-width: 32px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-left: 8px;
|
||||||
|
border-radius: 32px;
|
||||||
|
background-color: $theme-button-bg-color;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// to make alignment easier, create flexbox for the image
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenuButton_contextMenu_optionList {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
// This is a bit of a hack when we could just use a simple border-top property,
|
||||||
|
// however we have a (kinda) good reason for doing it this way: we need opacity.
|
||||||
|
// To get the right color, we need an opacity modifier which means we have to work
|
||||||
|
// around the problem. PostCSS doesn't support the opacity() function, and if we
|
||||||
|
// use something like postcss-functions we quickly run into an issue where the
|
||||||
|
// function we would define gets passed a CSS variable for custom themes, which
|
||||||
|
// can't be converted easily even when considering https://stackoverflow.com/a/41265350/7037379
|
||||||
|
//
|
||||||
|
// Therefore, we just hack in a line and border the thing ourselves
|
||||||
|
&::before {
|
||||||
|
border-top: 1px solid $primary-fg-color;
|
||||||
|
opacity: 0.1;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
// Counteract the padding problems (width: 100% ignores the 40px padding,
|
||||||
|
// unless we position it absolutely then it does the right thing).
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 0 0;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
text-decoration: none;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
// Create a flexbox to more easily define the list items
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
img { // icons
|
||||||
|
width: 16px;
|
||||||
|
min-width: 16px;
|
||||||
|
max-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span { // labels
|
||||||
|
padding-left: 14px;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
// Ellipsize any text overflow
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
res/css/views/rooms/_RoomList2.scss
Normal file
25
res/css/views/rooms/_RoomList2.scss
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Rename to mx_RoomList during replacement of old component
|
||||||
|
|
||||||
|
.mx_RoomList2 {
|
||||||
|
// Create a column-based flexbox for the sublists. That's pretty much all we have to
|
||||||
|
// worry about in this stylesheet.
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
|
@ -14,8 +14,42 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// TODO: Rename to mx_RoomSublist during replacement of old component
|
||||||
|
|
||||||
|
// TODO: Just use the 3 selectors we need from this instead of importing it.
|
||||||
|
// We're going to end up with heavy modifications anyways.
|
||||||
@import "../../../../node_modules/react-resizable/css/styles.css";
|
@import "../../../../node_modules/react-resizable/css/styles.css";
|
||||||
|
|
||||||
.mx_RoomList2 .mx_RoomSubList_labelContainer {
|
.mx_RoomSublist2 {
|
||||||
z-index: 12;
|
// The sublist is a column of rows, essentially
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.mx_RoomSublist2_headerContainer {
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.5;
|
||||||
|
line-height: $font-16px;
|
||||||
|
font-size: $font-12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSublist2_resizeBox {
|
||||||
|
// Create another flexbox column for the tiles
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.mx_RoomSublist2_showMoreButton {
|
||||||
|
height: 44px; // 1 room tile high
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// We create a flexbox to cheat at alignment
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
103
res/css/views/rooms/_RoomTile2.scss
Normal file
103
res/css/views/rooms/_RoomTile2.scss
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Rename to mx_RoomTile during replacement of old component
|
||||||
|
|
||||||
|
// Note: the room tile expects to be in a flexbox column container
|
||||||
|
.mx_RoomTile2 {
|
||||||
|
width: calc(100% - 11px); // 8px for padding (4px on either side), 3px for margin
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-right: 3px;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
// The tile is also a flexbox row itself
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&.mx_RoomTile2_selected {
|
||||||
|
background-color: $roomtile2-selected-bg-color;
|
||||||
|
border-radius: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile2_avatarContainer {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile2_nameContainer {
|
||||||
|
// Create a new column layout flexbox for the name parts
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.mx_RoomTile2_name,
|
||||||
|
.mx_RoomTile2_messagePreview {
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Ellipsis on the name and preview
|
||||||
|
|
||||||
|
.mx_RoomTile2_name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile2_messagePreview {
|
||||||
|
font-size: $font-13px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
color: $roomtile2-preview-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile2_badgeContainer {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
// Create another flexbox row because it's super easy to position the badge at
|
||||||
|
// the end this way.
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.mx_RoomTile2_badge {
|
||||||
|
background-color: $roomtile2-badge-color;
|
||||||
|
|
||||||
|
&:not(.mx_RoomTile2_badgeEmpty) {
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: $font-10px;
|
||||||
|
line-height: $font-14px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 14px;
|
||||||
|
color: #fff; // TODO: Variable
|
||||||
|
|
||||||
|
// TODO: Confirm padding on counted badges
|
||||||
|
padding: 2px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_RoomTile2_badgeEmpty {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-right: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_RoomTile2_badgeHighlight {
|
||||||
|
// TODO: Use a more specific variable
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
res/img/feather-customised/archive.svg
Normal file
1
res/img/feather-customised/archive.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-archive"><polyline points="21 8 21 21 3 21 3 8"></polyline><rect x="1" y="3" width="22" height="5"></rect><line x1="10" y1="12" x2="14" y2="12"></line></svg>
|
After Width: | Height: | Size: 361 B |
1
res/img/feather-customised/more-horizontal.svg
Normal file
1
res/img/feather-customised/more-horizontal.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-more-horizontal"><circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle></svg>
|
After Width: | Height: | Size: 343 B |
1
res/img/feather-customised/sun.svg
Normal file
1
res/img/feather-customised/sun.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
After Width: | Height: | Size: 650 B |
|
@ -172,6 +172,13 @@ $header-divider-color: #91A1C0;
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
|
|
||||||
|
// TODO: Update variables for new room list
|
||||||
|
// TODO: Dark theme
|
||||||
|
$roomtile2-preview-color: #9e9e9e;
|
||||||
|
$roomtile2-badge-color: #61708b;
|
||||||
|
$roomtile2-selected-bg-color: #FFF;
|
||||||
|
$theme-button-bg-color: #e3e8f0;
|
||||||
|
|
||||||
$roomtile-name-color: #61708b;
|
$roomtile-name-color: #61708b;
|
||||||
$roomtile-badge-fg-color: $accent-fg-color;
|
$roomtile-badge-fg-color: $accent-fg-color;
|
||||||
$roomtile-selected-color: #212121;
|
$roomtile-selected-color: #212121;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import RoomViewStore from './stores/RoomViewStore';
|
||||||
*/
|
*/
|
||||||
class ActiveRoomObserver {
|
class ActiveRoomObserver {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._listeners = {};
|
this._listeners = {}; // key=roomId, value=function(isActive:boolean)
|
||||||
|
|
||||||
this._activeRoomId = RoomViewStore.getRoomId();
|
this._activeRoomId = RoomViewStore.getRoomId();
|
||||||
// TODO: We could self-destruct when the last listener goes away, or at least
|
// TODO: We could self-destruct when the last listener goes away, or at least
|
||||||
|
@ -35,6 +35,10 @@ class ActiveRoomObserver {
|
||||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this));
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeRoomId(): string {
|
||||||
|
return this._activeRoomId;
|
||||||
|
}
|
||||||
|
|
||||||
addListener(roomId, listener) {
|
addListener(roomId, listener) {
|
||||||
if (!this._listeners[roomId]) this._listeners[roomId] = [];
|
if (!this._listeners[roomId]) this._listeners[roomId] = [];
|
||||||
this._listeners[roomId].push(listener);
|
this._listeners[roomId].push(listener);
|
||||||
|
@ -51,23 +55,23 @@ class ActiveRoomObserver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_emit(roomId) {
|
_emit(roomId, isActive: boolean) {
|
||||||
if (!this._listeners[roomId]) return;
|
if (!this._listeners[roomId]) return;
|
||||||
|
|
||||||
for (const l of this._listeners[roomId]) {
|
for (const l of this._listeners[roomId]) {
|
||||||
l.call();
|
l.call(null, isActive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRoomViewStoreUpdate() {
|
_onRoomViewStoreUpdate() {
|
||||||
// emit for the old room ID
|
// emit for the old room ID
|
||||||
if (this._activeRoomId) this._emit(this._activeRoomId);
|
if (this._activeRoomId) this._emit(this._activeRoomId, false);
|
||||||
|
|
||||||
// update our cache
|
// update our cache
|
||||||
this._activeRoomId = RoomViewStore.getRoomId();
|
this._activeRoomId = RoomViewStore.getRoomId();
|
||||||
|
|
||||||
// and emit for the new one
|
// and emit for the new one
|
||||||
if (this._activeRoomId) this._emit(this._activeRoomId);
|
if (this._activeRoomId) this._emit(this._activeRoomId, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,10 @@ import SearchBox from "./SearchBox";
|
||||||
import RoomList2 from "../views/rooms/RoomList2";
|
import RoomList2 from "../views/rooms/RoomList2";
|
||||||
import TopLeftMenuButton from "./TopLeftMenuButton";
|
import TopLeftMenuButton from "./TopLeftMenuButton";
|
||||||
import { Action } from "../../dispatcher/actions";
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
import BaseAvatar from '../views/avatars/BaseAvatar';
|
||||||
|
import RoomBreadcrumbs from "../views/rooms/RoomBreadcrumbs";
|
||||||
|
import UserMenuButton from "./UserMenuButton";
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
* CAUTION *
|
* CAUTION *
|
||||||
|
@ -46,7 +50,6 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
// TODO: Properly support TagPanel
|
// TODO: Properly support TagPanel
|
||||||
// TODO: Properly support searching/filtering
|
// TODO: Properly support searching/filtering
|
||||||
// TODO: Properly support breadcrumbs
|
// TODO: Properly support breadcrumbs
|
||||||
// TODO: Properly support TopLeftMenu (User Settings)
|
|
||||||
// TODO: a11y
|
// TODO: a11y
|
||||||
// TODO: actually make this useful in general (match design proposals)
|
// TODO: actually make this useful in general (match design proposals)
|
||||||
// TODO: Fadable support (is this still needed?)
|
// TODO: Fadable support (is this still needed?)
|
||||||
|
@ -82,24 +85,56 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderHeader(): React.ReactNode {
|
||||||
|
// TODO: Update when profile info changes
|
||||||
|
// TODO: Presence
|
||||||
|
// TODO: Breadcrumbs toggle
|
||||||
|
// TODO: Menu button
|
||||||
|
const avatarSize = 32;
|
||||||
|
// TODO: Don't do this profile lookup in render()
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
let displayName = client.getUserId();
|
||||||
|
let avatarUrl: string = null;
|
||||||
|
const myUser = client.getUser(client.getUserId());
|
||||||
|
if (myUser) {
|
||||||
|
displayName = myUser.rawDisplayName;
|
||||||
|
avatarUrl = myUser.avatarUrl;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mx_LeftPanel2_userHeader">
|
||||||
|
<div className="mx_LeftPanel2_headerRow">
|
||||||
|
<span className="mx_LeftPanel2_userAvatarContainer">
|
||||||
|
<BaseAvatar
|
||||||
|
idName={MatrixClientPeg.get().getUserId()}
|
||||||
|
name={displayName}
|
||||||
|
url={avatarUrl}
|
||||||
|
width={avatarSize}
|
||||||
|
height={avatarSize}
|
||||||
|
resizeMethod="crop"
|
||||||
|
className="mx_LeftPanel2_userAvatar"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className="mx_LeftPanel2_userName">{displayName}</span>
|
||||||
|
<span className="mx_LeftPanel2_headerButtons">
|
||||||
|
<UserMenuButton />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mx_LeftPanel2_headerRow mx_LeftPanel2_breadcrumbsContainer">
|
||||||
|
<RoomBreadcrumbs />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const tagPanel = (
|
const tagPanel = (
|
||||||
<div className="mx_LeftPanel_tagPanelContainer">
|
<div className="mx_LeftPanel2_tagPanelContainer">
|
||||||
<TagPanel/>
|
<TagPanel/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const exploreButton = (
|
|
||||||
<div
|
|
||||||
className={classNames("mx_LeftPanel_explore", {"mx_LeftPanel_explore_hidden": this.state.searchExpanded})}>
|
|
||||||
<AccessibleButton onClick={() => dis.dispatch({action: 'view_room_directory'})}>
|
|
||||||
{_t("Explore")}
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const searchBox = (<SearchBox
|
const searchBox = (<SearchBox
|
||||||
className="mx_LeftPanel_filterRooms"
|
className="mx_LeftPanel2_filterRoomsSearch"
|
||||||
enableRoomSearchFocus={true}
|
enableRoomSearchFocus={true}
|
||||||
blurredPlaceholder={_t('Filter')}
|
blurredPlaceholder={_t('Filter')}
|
||||||
placeholder={_t('Filter rooms…')}
|
placeholder={_t('Filter rooms…')}
|
||||||
|
@ -124,29 +159,25 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
// TODO: Conference handling / calls
|
// TODO: Conference handling / calls
|
||||||
|
|
||||||
const containerClasses = classNames({
|
const containerClasses = classNames({
|
||||||
"mx_LeftPanel_container": true,
|
"mx_LeftPanel2": true,
|
||||||
"mx_fadable": true,
|
|
||||||
"collapsed": false, // TODO: Collapsed support
|
|
||||||
"mx_LeftPanel_container_hasTagPanel": true, // TODO: TagPanel support
|
|
||||||
"mx_fadable_faded": false,
|
|
||||||
"mx_LeftPanel2": true, // TODO: Remove flag when RoomList2 ships (used as an indicator)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{tagPanel}
|
{tagPanel}
|
||||||
<aside className="mx_LeftPanel dark-panel">
|
<aside className="mx_LeftPanel2_roomListContainer">
|
||||||
<TopLeftMenuButton collapsed={false}/>
|
{this.renderHeader()}
|
||||||
<div
|
<div
|
||||||
className="mx_LeftPanel_exploreAndFilterRow"
|
className="mx_LeftPanel2_filterContainer"
|
||||||
onKeyDown={() => {/*TODO*/}}
|
onKeyDown={() => {/*TODO*/}}
|
||||||
onFocus={() => {/*TODO*/}}
|
onFocus={() => {/*TODO*/}}
|
||||||
onBlur={() => {/*TODO*/}}
|
onBlur={() => {/*TODO*/}}
|
||||||
>
|
>
|
||||||
{exploreButton}
|
|
||||||
{searchBox}
|
{searchBox}
|
||||||
</div>
|
</div>
|
||||||
{roomList}
|
<div className="mx_LeftPanel2_actualRoomListContainer">
|
||||||
|
{roomList}
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -452,9 +452,7 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
// composer, so CTRL+` it is
|
// composer, so CTRL+` it is
|
||||||
|
|
||||||
if (ctrlCmdOnly) {
|
if (ctrlCmdOnly) {
|
||||||
dis.dispatch({
|
dis.fire(Action.ToggleUserMenu);
|
||||||
action: 'toggle_top_left_menu',
|
|
||||||
});
|
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -72,6 +72,7 @@ import {
|
||||||
hideToast as hideAnalyticsToast
|
hideToast as hideAnalyticsToast
|
||||||
} from "../../toasts/AnalyticsToast";
|
} from "../../toasts/AnalyticsToast";
|
||||||
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||||
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -604,9 +605,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.viewIndexedRoom(payload.roomIndex);
|
this.viewIndexedRoom(payload.roomIndex);
|
||||||
break;
|
break;
|
||||||
case Action.ViewUserSettings: {
|
case Action.ViewUserSettings: {
|
||||||
|
const tabPayload = payload as OpenToTabPayload;
|
||||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {},
|
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
{initialTabId: tabPayload.initialTabId},
|
||||||
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true
|
||||||
|
);
|
||||||
|
|
||||||
// View the welcome or home page if we need something to look at
|
// View the welcome or home page if we need something to look at
|
||||||
this.viewSomethingBehindModal();
|
this.viewSomethingBehindModal();
|
||||||
|
|
|
@ -27,25 +27,20 @@ import { ReactNode } from "react";
|
||||||
* Represents a tab for the TabbedView.
|
* Represents a tab for the TabbedView.
|
||||||
*/
|
*/
|
||||||
export class Tab {
|
export class Tab {
|
||||||
public label: string;
|
|
||||||
public icon: string;
|
|
||||||
public body: React.ReactNode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new tab.
|
* Creates a new tab.
|
||||||
* @param {string} tabLabel The untranslated tab label.
|
* @param {string} id The tab's ID.
|
||||||
* @param {string} tabIconClass The class for the tab icon. This should be a simple mask.
|
* @param {string} label The untranslated tab label.
|
||||||
* @param {React.ReactNode} tabJsx The JSX for the tab container.
|
* @param {string} icon The class for the tab icon. This should be a simple mask.
|
||||||
|
* @param {React.ReactNode} body The JSX for the tab container.
|
||||||
*/
|
*/
|
||||||
constructor(tabLabel: string, tabIconClass: string, tabJsx: React.ReactNode) {
|
constructor(public id: string, public label: string, public icon: string, public body: React.ReactNode) {
|
||||||
this.label = tabLabel;
|
|
||||||
this.icon = tabIconClass;
|
|
||||||
this.body = tabJsx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tabs: Tab[];
|
tabs: Tab[];
|
||||||
|
initialTabId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -53,16 +48,17 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TabbedView extends React.Component<IProps, IState> {
|
export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
|
||||||
// The tabs to show
|
|
||||||
tabs: PropTypes.arrayOf(PropTypes.instanceOf(Tab)).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
let activeTabIndex = 0;
|
||||||
|
if (props.initialTabId) {
|
||||||
|
const tabIndex = props.tabs.findIndex(t => t.id === props.initialTabId);
|
||||||
|
if (tabIndex >= 0) activeTabIndex = tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
activeTabIndex: 0,
|
activeTabIndex,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import * as Avatar from '../../Avatar';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
||||||
|
import {Action} from "../../dispatcher/actions";
|
||||||
|
|
||||||
const AVATAR_SIZE = 28;
|
const AVATAR_SIZE = 28;
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ export default class TopLeftMenuButton extends React.Component {
|
||||||
|
|
||||||
onAction = (payload) => {
|
onAction = (payload) => {
|
||||||
// For accessibility
|
// For accessibility
|
||||||
if (payload.action === "toggle_top_left_menu") {
|
if (payload.action === Action.ToggleUserMenu) {
|
||||||
if (this._buttonRef) this._buttonRef.click();
|
if (this._buttonRef) this._buttonRef.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
270
src/components/structures/UserMenuButton.tsx
Normal file
270
src/components/structures/UserMenuButton.tsx
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 * as React from "react";
|
||||||
|
import {User} from "matrix-js-sdk/src/models/user";
|
||||||
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import { createRef } from "react";
|
||||||
|
import { _t } from "../../languageHandler";
|
||||||
|
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
||||||
|
import {USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB} from "../views/dialogs/UserSettingsDialog";
|
||||||
|
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||||
|
import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog";
|
||||||
|
import Modal from "../../Modal";
|
||||||
|
import LogoutDialog from "../views/dialogs/LogoutDialog";
|
||||||
|
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
|
||||||
|
import {getCustomTheme} from "../../theme";
|
||||||
|
import {getHostingLink} from "../../utils/HostingLink";
|
||||||
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
user: User;
|
||||||
|
menuDisplayed: boolean;
|
||||||
|
isDarkTheme: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UserMenuButton extends React.Component<IProps, IState> {
|
||||||
|
private dispatcherRef: string;
|
||||||
|
private themeWatcherRef: string;
|
||||||
|
private buttonRef: React.RefObject<HTMLButtonElement> = createRef();
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
menuDisplayed: false,
|
||||||
|
user: MatrixClientPeg.get().getUser(MatrixClientPeg.get().getUserId()),
|
||||||
|
isDarkTheme: this.isUserOnDarkTheme(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private get displayName(): string {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return _t("Guest");
|
||||||
|
} else if (this.state.user) {
|
||||||
|
return this.state.user.displayName;
|
||||||
|
} else {
|
||||||
|
return MatrixClientPeg.get().getUserId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidMount() {
|
||||||
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
|
this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef);
|
||||||
|
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isUserOnDarkTheme(): boolean {
|
||||||
|
const theme = SettingsStore.getValue("theme");
|
||||||
|
if (theme.startsWith("custom-")) {
|
||||||
|
return getCustomTheme(theme.substring(0, 7)).is_dark;
|
||||||
|
}
|
||||||
|
return theme === "dark";
|
||||||
|
}
|
||||||
|
|
||||||
|
private onThemeChanged = () => {
|
||||||
|
this.setState({isDarkTheme: this.isUserOnDarkTheme()});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAction = (ev: ActionPayload) => {
|
||||||
|
if (ev.action !== Action.ToggleUserMenu) return; // not interested
|
||||||
|
|
||||||
|
// For accessibility
|
||||||
|
if (this.buttonRef.current) this.buttonRef.current.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
private onOpenMenuClick = (ev: InputEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.setState({menuDisplayed: true});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCloseMenu = () => {
|
||||||
|
this.setState({menuDisplayed: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSwitchThemeClick = () => {
|
||||||
|
// Disable system theme matching if the user hits this button
|
||||||
|
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, false);
|
||||||
|
|
||||||
|
const newTheme = this.state.isDarkTheme ? "light" : "dark";
|
||||||
|
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSettingsOpen = (ev: React.MouseEvent, tabId: string) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId};
|
||||||
|
defaultDispatcher.dispatch(payload);
|
||||||
|
this.setState({menuDisplayed: false}); // also close the menu
|
||||||
|
};
|
||||||
|
|
||||||
|
private onShowArchived = (ev: React.MouseEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
// TODO: Archived room view (deferred)
|
||||||
|
console.log("TODO: Show archived rooms");
|
||||||
|
};
|
||||||
|
|
||||||
|
private onProvideFeedback = (ev: React.MouseEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
||||||
|
this.setState({menuDisplayed: false}); // also close the menu
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSignOutClick = (ev: React.MouseEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
|
||||||
|
this.setState({menuDisplayed: false}); // also close the menu
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
let contextMenu;
|
||||||
|
if (this.state.menuDisplayed) {
|
||||||
|
let hostingLink;
|
||||||
|
const signupLink = getHostingLink("user-context-menu");
|
||||||
|
if (signupLink) {
|
||||||
|
hostingLink = (
|
||||||
|
<div className="mx_UserMenuButton_contextMenu_header">
|
||||||
|
{_t(
|
||||||
|
"<a>Upgrade</a> to your own domain", {},
|
||||||
|
{
|
||||||
|
a: sub => (
|
||||||
|
<a
|
||||||
|
href={signupLink}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
tabIndex={-1}
|
||||||
|
>{sub}</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementRect = this.buttonRef.current.getBoundingClientRect();
|
||||||
|
contextMenu = (
|
||||||
|
<ContextMenu
|
||||||
|
chevronFace="none"
|
||||||
|
left={elementRect.left}
|
||||||
|
top={elementRect.top + elementRect.height}
|
||||||
|
onFinished={this.onCloseMenu}
|
||||||
|
>
|
||||||
|
<div className="mx_UserMenuButton_contextMenu">
|
||||||
|
<div className="mx_UserMenuButton_contextMenu_header">
|
||||||
|
<div className="mx_UserMenuButton_contextMenu_name">
|
||||||
|
<span className="mx_UserMenuButton_contextMenu_displayName">
|
||||||
|
{this.displayName}
|
||||||
|
</span>
|
||||||
|
<span className="mx_UserMenuButton_contextMenu_userId">
|
||||||
|
{MatrixClientPeg.get().getUserId()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mx_UserMenuButton_contextMenu_themeButton"
|
||||||
|
onClick={this.onSwitchThemeClick}
|
||||||
|
title={this.state.isDarkTheme ? _t("Switch to light mode") : _t("Switch to dark mode")}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={require("../../../res/img/feather-customised/sun.svg")}
|
||||||
|
alt={_t("Switch theme")}
|
||||||
|
width={16}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{hostingLink}
|
||||||
|
<div className="mx_UserMenuButton_contextMenu_optionList">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={(e) => this.onSettingsOpen(e, USER_NOTIFICATIONS_TAB)}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/notifications.svg")} width={16} />
|
||||||
|
<span>{_t("Notification settings")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={(e) => this.onSettingsOpen(e, USER_SECURITY_TAB)}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/lock.svg")} width={16} />
|
||||||
|
<span>{_t("Security & privacy")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={(e) => this.onSettingsOpen(e, null)}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/settings.svg")} width={16} />
|
||||||
|
<span>{_t("All settings")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={this.onShowArchived}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/archive.svg")} width={16} />
|
||||||
|
<span>{_t("Archived rooms")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={this.onProvideFeedback}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/message-circle.svg")} width={16} />
|
||||||
|
<span>{_t("Feedback")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="mx_UserMenuButton_contextMenu_optionList">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<AccessibleButton onClick={this.onSignOutClick}>
|
||||||
|
<img src={require("../../../res/img/feather-customised/sign-out.svg")} width={16} />
|
||||||
|
<span>{_t("Sign out")}</span>
|
||||||
|
</AccessibleButton>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ContextMenuButton
|
||||||
|
className="mx_UserMenuButton"
|
||||||
|
onClick={this.onOpenMenuClick}
|
||||||
|
inputRef={this.buttonRef}
|
||||||
|
label={_t("Account settings")}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
>
|
||||||
|
<img src={require("../../../res/img/feather-customised/more-horizontal.svg")} alt="..." width={14} />
|
||||||
|
</ContextMenuButton>
|
||||||
|
{contextMenu}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,13 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
|
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
||||||
|
export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB";
|
||||||
|
export const ROOM_ROLES_TAB = "ROOM_ROLES_TAB";
|
||||||
|
export const ROOM_NOTIFICATIONS_TAB = "ROOM_NOTIFICATIONS_TAB";
|
||||||
|
export const ROOM_BRIDGES_TAB = "ROOM_BRIDGES_TAB";
|
||||||
|
export const ROOM_ADVANCED_TAB = "ROOM_ADVANCED_TAB";
|
||||||
|
|
||||||
export default class RoomSettingsDialog extends React.Component {
|
export default class RoomSettingsDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
|
@ -56,21 +63,25 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_GENERAL_TAB,
|
||||||
_td("General"),
|
_td("General"),
|
||||||
"mx_RoomSettingsDialog_settingsIcon",
|
"mx_RoomSettingsDialog_settingsIcon",
|
||||||
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
|
<GeneralRoomSettingsTab roomId={this.props.roomId} />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_SECURITY_TAB,
|
||||||
_td("Security & Privacy"),
|
_td("Security & Privacy"),
|
||||||
"mx_RoomSettingsDialog_securityIcon",
|
"mx_RoomSettingsDialog_securityIcon",
|
||||||
<SecurityRoomSettingsTab roomId={this.props.roomId} />,
|
<SecurityRoomSettingsTab roomId={this.props.roomId} />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_ROLES_TAB,
|
||||||
_td("Roles & Permissions"),
|
_td("Roles & Permissions"),
|
||||||
"mx_RoomSettingsDialog_rolesIcon",
|
"mx_RoomSettingsDialog_rolesIcon",
|
||||||
<RolesRoomSettingsTab roomId={this.props.roomId} />,
|
<RolesRoomSettingsTab roomId={this.props.roomId} />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_NOTIFICATIONS_TAB,
|
||||||
_td("Notifications"),
|
_td("Notifications"),
|
||||||
"mx_RoomSettingsDialog_notificationsIcon",
|
"mx_RoomSettingsDialog_notificationsIcon",
|
||||||
<NotificationSettingsTab roomId={this.props.roomId} />,
|
<NotificationSettingsTab roomId={this.props.roomId} />,
|
||||||
|
@ -78,6 +89,7 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
|
|
||||||
if (SettingsStore.isFeatureEnabled("feature_bridge_state")) {
|
if (SettingsStore.isFeatureEnabled("feature_bridge_state")) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_BRIDGES_TAB,
|
||||||
_td("Bridges"),
|
_td("Bridges"),
|
||||||
"mx_RoomSettingsDialog_bridgesIcon",
|
"mx_RoomSettingsDialog_bridgesIcon",
|
||||||
<BridgeSettingsTab roomId={this.props.roomId} />,
|
<BridgeSettingsTab roomId={this.props.roomId} />,
|
||||||
|
@ -85,6 +97,7 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
ROOM_ADVANCED_TAB,
|
||||||
_td("Advanced"),
|
_td("Advanced"),
|
||||||
"mx_RoomSettingsDialog_warningIcon",
|
"mx_RoomSettingsDialog_warningIcon",
|
||||||
<AdvancedRoomSettingsTab roomId={this.props.roomId} closeSettingsFn={this.props.onFinished} />,
|
<AdvancedRoomSettingsTab roomId={this.props.roomId} closeSettingsFn={this.props.onFinished} />,
|
||||||
|
|
|
@ -33,9 +33,21 @@ import * as sdk from "../../../index";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
||||||
|
|
||||||
|
export const USER_GENERAL_TAB = "USER_GENERAL_TAB";
|
||||||
|
export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB";
|
||||||
|
export const USER_FLAIR_TAB = "USER_FLAIR_TAB";
|
||||||
|
export const USER_NOTIFICATIONS_TAB = "USER_NOTIFICATIONS_TAB";
|
||||||
|
export const USER_PREFERENCES_TAB = "USER_PREFERENCES_TAB";
|
||||||
|
export const USER_VOICE_TAB = "USER_VOICE_TAB";
|
||||||
|
export const USER_SECURITY_TAB = "USER_SECURITY_TAB";
|
||||||
|
export const USER_LABS_TAB = "USER_LABS_TAB";
|
||||||
|
export const USER_MJOLNIR_TAB = "USER_MJOLNIR_TAB";
|
||||||
|
export const USER_HELP_TAB = "USER_HELP_TAB";
|
||||||
|
|
||||||
export default class UserSettingsDialog extends React.Component {
|
export default class UserSettingsDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
initialTabId: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -63,42 +75,50 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_GENERAL_TAB,
|
||||||
_td("General"),
|
_td("General"),
|
||||||
"mx_UserSettingsDialog_settingsIcon",
|
"mx_UserSettingsDialog_settingsIcon",
|
||||||
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_APPEARANCE_TAB,
|
||||||
_td("Appearance"),
|
_td("Appearance"),
|
||||||
"mx_UserSettingsDialog_appearanceIcon",
|
"mx_UserSettingsDialog_appearanceIcon",
|
||||||
<AppearanceUserSettingsTab />,
|
<AppearanceUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_FLAIR_TAB,
|
||||||
_td("Flair"),
|
_td("Flair"),
|
||||||
"mx_UserSettingsDialog_flairIcon",
|
"mx_UserSettingsDialog_flairIcon",
|
||||||
<FlairUserSettingsTab />,
|
<FlairUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_NOTIFICATIONS_TAB,
|
||||||
_td("Notifications"),
|
_td("Notifications"),
|
||||||
"mx_UserSettingsDialog_bellIcon",
|
"mx_UserSettingsDialog_bellIcon",
|
||||||
<NotificationUserSettingsTab />,
|
<NotificationUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_PREFERENCES_TAB,
|
||||||
_td("Preferences"),
|
_td("Preferences"),
|
||||||
"mx_UserSettingsDialog_preferencesIcon",
|
"mx_UserSettingsDialog_preferencesIcon",
|
||||||
<PreferencesUserSettingsTab />,
|
<PreferencesUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_VOICE_TAB,
|
||||||
_td("Voice & Video"),
|
_td("Voice & Video"),
|
||||||
"mx_UserSettingsDialog_voiceIcon",
|
"mx_UserSettingsDialog_voiceIcon",
|
||||||
<VoiceUserSettingsTab />,
|
<VoiceUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_SECURITY_TAB,
|
||||||
_td("Security & Privacy"),
|
_td("Security & Privacy"),
|
||||||
"mx_UserSettingsDialog_securityIcon",
|
"mx_UserSettingsDialog_securityIcon",
|
||||||
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
));
|
));
|
||||||
if (SdkConfig.get()['showLabsSettings'] || SettingsStore.getLabsFeatures().length > 0) {
|
if (SdkConfig.get()['showLabsSettings'] || SettingsStore.getLabsFeatures().length > 0) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_LABS_TAB,
|
||||||
_td("Labs"),
|
_td("Labs"),
|
||||||
"mx_UserSettingsDialog_labsIcon",
|
"mx_UserSettingsDialog_labsIcon",
|
||||||
<LabsUserSettingsTab />,
|
<LabsUserSettingsTab />,
|
||||||
|
@ -106,12 +126,14 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
}
|
}
|
||||||
if (this.state.mjolnirEnabled) {
|
if (this.state.mjolnirEnabled) {
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_MJOLNIR_TAB,
|
||||||
_td("Ignored users"),
|
_td("Ignored users"),
|
||||||
"mx_UserSettingsDialog_mjolnirIcon",
|
"mx_UserSettingsDialog_mjolnirIcon",
|
||||||
<MjolnirUserSettingsTab />,
|
<MjolnirUserSettingsTab />,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
USER_HELP_TAB,
|
||||||
_td("Help & About"),
|
_td("Help & About"),
|
||||||
"mx_UserSettingsDialog_helpIcon",
|
"mx_UserSettingsDialog_helpIcon",
|
||||||
<HelpUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<HelpUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
|
@ -127,7 +149,7 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
<BaseDialog className='mx_UserSettingsDialog' hasCancel={true}
|
<BaseDialog className='mx_UserSettingsDialog' hasCancel={true}
|
||||||
onFinished={this.props.onFinished} title={_t("Settings")}>
|
onFinished={this.props.onFinished} title={_t("Settings")}>
|
||||||
<div className='ms_SettingsDialog_content'>
|
<div className='ms_SettingsDialog_content'>
|
||||||
<TabbedView tabs={this._getTabs()} />
|
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,7 +96,7 @@ const TAG_AESTHETICS: {
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
},
|
},
|
||||||
[DefaultTagID.DM]: {
|
[DefaultTagID.DM]: {
|
||||||
sectionLabel: _td("Direct Messages"),
|
sectionLabel: _td("People"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
addRoomLabel: _td("Start chat"),
|
addRoomLabel: _td("Start chat"),
|
||||||
|
@ -200,6 +200,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
||||||
addRoomLabel={aesthetics.addRoomLabel}
|
addRoomLabel={aesthetics.addRoomLabel}
|
||||||
isInvite={aesthetics.isInvite}
|
isInvite={aesthetics.isInvite}
|
||||||
layout={this.state.layouts.get(orderedTagId)}
|
layout={this.state.layouts.get(orderedTagId)}
|
||||||
|
showMessagePreviews={orderedTagId === DefaultTagID.DM}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -216,7 +217,7 @@ export default class RoomList2 extends React.Component<IProps, IState> {
|
||||||
onFocus={this.props.onFocus}
|
onFocus={this.props.onFocus}
|
||||||
onBlur={this.props.onBlur}
|
onBlur={this.props.onBlur}
|
||||||
onKeyDown={onKeyDownHandler}
|
onKeyDown={onKeyDownHandler}
|
||||||
className="mx_RoomList mx_RoomList2"
|
className="mx_RoomList2"
|
||||||
role="tree"
|
role="tree"
|
||||||
aria-label={_t("Rooms")}
|
aria-label={_t("Rooms")}
|
||||||
// Firefox sometimes makes this element focusable due to
|
// Firefox sometimes makes this element focusable due to
|
||||||
|
|
|
@ -20,15 +20,13 @@ import * as React from "react";
|
||||||
import { createRef } from "react";
|
import { createRef } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as RoomNotifs from '../../../RoomNotifs';
|
|
||||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||||
import AccessibleTooltipButton from "../../views/elements/AccessibleTooltipButton";
|
|
||||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
|
||||||
import RoomTile2 from "./RoomTile2";
|
import RoomTile2 from "./RoomTile2";
|
||||||
import { ResizableBox, ResizeCallbackData } from "react-resizable";
|
import { ResizableBox, ResizeCallbackData } from "react-resizable";
|
||||||
import { ListLayout } from "../../../stores/room-list/ListLayout";
|
import { ListLayout } from "../../../stores/room-list/ListLayout";
|
||||||
|
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
* CAUTION *
|
* CAUTION *
|
||||||
|
@ -43,6 +41,7 @@ interface IProps {
|
||||||
rooms?: Room[];
|
rooms?: Room[];
|
||||||
startAsHidden: boolean;
|
startAsHidden: boolean;
|
||||||
label: string;
|
label: string;
|
||||||
|
showMessagePreviews: boolean;
|
||||||
onAddRoom?: () => void;
|
onAddRoom?: () => void;
|
||||||
addRoomLabel: string;
|
addRoomLabel: string;
|
||||||
isInvite: boolean;
|
isInvite: boolean;
|
||||||
|
@ -93,7 +92,13 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (this.props.rooms) {
|
if (this.props.rooms) {
|
||||||
for (const room of this.props.rooms) {
|
for (const room of this.props.rooms) {
|
||||||
tiles.push(<RoomTile2 room={room} key={`room-${room.roomId}`}/>);
|
tiles.push(
|
||||||
|
<RoomTile2
|
||||||
|
room={room}
|
||||||
|
key={`room-${room.roomId}`}
|
||||||
|
showMessagePreview={this.props.showMessagePreviews}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,25 +106,16 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderHeader(): React.ReactElement {
|
private renderHeader(): React.ReactElement {
|
||||||
const notifications = !this.props.isInvite
|
// TODO: Handle badge count
|
||||||
? RoomNotifs.aggregateNotificationCount(this.props.rooms)
|
// const notifications = !this.props.isInvite
|
||||||
: {count: 0, highlight: true};
|
// ? RoomNotifs.aggregateNotificationCount(this.props.rooms)
|
||||||
const notifCount = notifications.count;
|
// : {count: 0, highlight: true};
|
||||||
const notifHighlight = notifications.highlight;
|
// const notifCount = notifications.count;
|
||||||
|
// const notifHighlight = notifications.highlight;
|
||||||
|
|
||||||
// TODO: Title on collapsed
|
// TODO: Title on collapsed
|
||||||
// TODO: Incoming call box
|
// TODO: Incoming call box
|
||||||
|
|
||||||
let chevron = null;
|
|
||||||
if (this.hasTiles()) {
|
|
||||||
const chevronClasses = classNames({
|
|
||||||
'mx_RoomSubList_chevron': true,
|
|
||||||
'mx_RoomSubList_chevronRight': false, // isCollapsed
|
|
||||||
'mx_RoomSubList_chevronDown': true, // !isCollapsed
|
|
||||||
});
|
|
||||||
chevron = (<div className={chevronClasses}/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RovingTabIndexWrapper inputRef={this.headerButton}>
|
<RovingTabIndexWrapper inputRef={this.headerButton}>
|
||||||
{({onFocus, isActive, ref}) => {
|
{({onFocus, isActive, ref}) => {
|
||||||
|
@ -127,68 +123,68 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
const tabIndex = isActive ? 0 : -1;
|
const tabIndex = isActive ? 0 : -1;
|
||||||
|
|
||||||
// TODO: Collapsed state
|
// TODO: Collapsed state
|
||||||
let badge;
|
// TODO: Handle badge count
|
||||||
if (true) { // !isCollapsed
|
// let badge;
|
||||||
const badgeClasses = classNames({
|
// if (true) { // !isCollapsed
|
||||||
'mx_RoomSubList_badge': true,
|
// const showCount = localStorage.getItem("mx_rls_count") || notifHighlight;
|
||||||
'mx_RoomSubList_badgeHighlight': notifHighlight,
|
// const badgeClasses = classNames({
|
||||||
});
|
// 'mx_RoomSublist2_badge': true,
|
||||||
// Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
// 'mx_RoomSublist2_badgeHighlight': notifHighlight,
|
||||||
if (notifCount > 0) {
|
// 'mx_RoomSublist2_badgeEmpty': !showCount,
|
||||||
badge = (
|
// });
|
||||||
<AccessibleButton
|
// // Wrap the contents in a div and apply styles to the child div so that the browser default outline works
|
||||||
tabIndex={tabIndex}
|
// if (notifCount > 0) {
|
||||||
className={badgeClasses}
|
// const count = <div>{FormattingUtils.formatCount(notifCount)}</div>;
|
||||||
aria-label={_t("Jump to first unread room.")}
|
// badge = (
|
||||||
>
|
// <AccessibleButton
|
||||||
<div>
|
// tabIndex={tabIndex}
|
||||||
{FormattingUtils.formatCount(notifCount)}
|
// className={badgeClasses}
|
||||||
</div>
|
// aria-label={_t("Jump to first unread room.")}
|
||||||
</AccessibleButton>
|
// >
|
||||||
);
|
// {showCount ? count : null}
|
||||||
} else if (this.props.isInvite && this.hasTiles()) {
|
// </AccessibleButton>
|
||||||
// Render the `!` badge for invites
|
// );
|
||||||
badge = (
|
// } else if (this.props.isInvite && this.hasTiles()) {
|
||||||
<AccessibleButton
|
// // Render the `!` badge for invites
|
||||||
tabIndex={tabIndex}
|
// badge = (
|
||||||
className={badgeClasses}
|
// <AccessibleButton
|
||||||
aria-label={_t("Jump to first invite.")}
|
// tabIndex={tabIndex}
|
||||||
>
|
// className={badgeClasses}
|
||||||
<div>
|
// aria-label={_t("Jump to first invite.")}
|
||||||
{FormattingUtils.formatCount(this.numTiles)}
|
// >
|
||||||
</div>
|
// <div>
|
||||||
</AccessibleButton>
|
// {FormattingUtils.formatCount(this.numTiles)}
|
||||||
);
|
// </div>
|
||||||
}
|
// </AccessibleButton>
|
||||||
}
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
let addRoomButton = null;
|
// TODO: Aux button
|
||||||
if (!!this.props.onAddRoom) {
|
// let addRoomButton = null;
|
||||||
addRoomButton = (
|
// if (!!this.props.onAddRoom) {
|
||||||
<AccessibleTooltipButton
|
// addRoomButton = (
|
||||||
tabIndex={tabIndex}
|
// <AccessibleTooltipButton
|
||||||
onClick={this.onAddRoom}
|
// tabIndex={tabIndex}
|
||||||
className="mx_RoomSubList_addRoom"
|
// onClick={this.onAddRoom}
|
||||||
title={this.props.addRoomLabel || _t("Add room")}
|
// className="mx_RoomSublist2_addButton"
|
||||||
/>
|
// title={this.props.addRoomLabel || _t("Add room")}
|
||||||
);
|
// />
|
||||||
}
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
// TODO: a11y (see old component)
|
// TODO: a11y (see old component)
|
||||||
return (
|
return (
|
||||||
<div className={"mx_RoomSubList_labelContainer"}>
|
<div className={"mx_RoomSublist2_headerContainer"}>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
className={"mx_RoomSubList_label"}
|
className={"mx_RoomSublist2_headerText"}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
aria-level="1"
|
aria-level="1"
|
||||||
>
|
>
|
||||||
{chevron}
|
|
||||||
<span>{this.props.label}</span>
|
<span>{this.props.label}</span>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{badge}
|
|
||||||
{addRoomButton}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
@ -204,9 +200,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
// TODO: Proper collapse support
|
// TODO: Proper collapse support
|
||||||
'mx_RoomSubList': true,
|
'mx_RoomSublist2': true,
|
||||||
'mx_RoomSubList_hidden': false, // len && isCollapsed
|
'mx_RoomSublist2_collapsed': false, // len && isCollapsed
|
||||||
'mx_RoomSubList_nonEmpty': this.hasTiles(), // len && !isCollapsed
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let content = null;
|
let content = null;
|
||||||
|
@ -244,7 +239,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
visibleTiles.splice(visibleTiles.length - 1, 1, (
|
visibleTiles.splice(visibleTiles.length - 1, 1, (
|
||||||
<div
|
<div
|
||||||
onClick={this.onShowAllClick}
|
onClick={this.onShowAllClick}
|
||||||
style={{height: '34px', lineHeight: '34px', cursor: 'pointer'}}
|
className='mx_RoomSublist2_showMoreButton'
|
||||||
key='showall'
|
key='showall'
|
||||||
>
|
>
|
||||||
{_t("Show %(n)s more", {n: numMissing})}
|
{_t("Show %(n)s more", {n: numMissing})}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import classNames from "classnames";
|
||||||
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
|
||||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||||
import RoomAvatar from "../../views/avatars/RoomAvatar";
|
import RoomAvatar from "../../views/avatars/RoomAvatar";
|
||||||
import Tooltip from "../../views/elements/Tooltip";
|
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { Key } from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
import * as RoomNotifs from '../../../RoomNotifs';
|
import * as RoomNotifs from '../../../RoomNotifs';
|
||||||
|
@ -32,6 +31,7 @@ import * as Unread from '../../../Unread';
|
||||||
import * as FormattingUtils from "../../../utils/FormattingUtils";
|
import * as FormattingUtils from "../../../utils/FormattingUtils";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
||||||
|
|
||||||
/*******************************************************************
|
/*******************************************************************
|
||||||
* CAUTION *
|
* CAUTION *
|
||||||
|
@ -51,6 +51,7 @@ enum NotificationColor {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
showMessagePreview: boolean;
|
||||||
|
|
||||||
// TODO: Allow falsifying counts (for invites and stuff)
|
// TODO: Allow falsifying counts (for invites and stuff)
|
||||||
// TODO: Transparency? Was this ever used?
|
// TODO: Transparency? Was this ever used?
|
||||||
|
@ -65,6 +66,7 @@ interface INotificationState {
|
||||||
interface IState {
|
interface IState {
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
notificationState: INotificationState;
|
notificationState: INotificationState;
|
||||||
|
selected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomTile2 extends React.Component<IProps, IState> {
|
export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
|
@ -87,12 +89,14 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
hover: false,
|
hover: false,
|
||||||
notificationState: this.getNotificationState(),
|
notificationState: this.getNotificationState(),
|
||||||
|
selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.props.room.on("Room.receipt", this.handleRoomEventUpdate);
|
this.props.room.on("Room.receipt", this.handleRoomEventUpdate);
|
||||||
this.props.room.on("Room.timeline", this.handleRoomEventUpdate);
|
this.props.room.on("Room.timeline", this.handleRoomEventUpdate);
|
||||||
this.props.room.on("Room.redaction", this.handleRoomEventUpdate);
|
this.props.room.on("Room.redaction", this.handleRoomEventUpdate);
|
||||||
MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate);
|
MatrixClientPeg.get().on("Event.decrypted", this.handleRoomEventUpdate);
|
||||||
|
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
|
@ -100,6 +104,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
this.props.room.removeListener("Room.receipt", this.handleRoomEventUpdate);
|
this.props.room.removeListener("Room.receipt", this.handleRoomEventUpdate);
|
||||||
this.props.room.removeListener("Room.timeline", this.handleRoomEventUpdate);
|
this.props.room.removeListener("Room.timeline", this.handleRoomEventUpdate);
|
||||||
this.props.room.removeListener("Room.redaction", this.handleRoomEventUpdate);
|
this.props.room.removeListener("Room.redaction", this.handleRoomEventUpdate);
|
||||||
|
ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
}
|
}
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate);
|
MatrixClientPeg.get().removeListener("Event.decrypted", this.handleRoomEventUpdate);
|
||||||
|
@ -186,39 +191,33 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onActiveRoomUpdate = (isActive: boolean) => {
|
||||||
|
this.setState({selected: isActive});
|
||||||
|
};
|
||||||
|
|
||||||
public render(): React.ReactElement {
|
public render(): React.ReactElement {
|
||||||
// TODO: Collapsed state
|
// TODO: Collapsed state
|
||||||
// TODO: Invites
|
// TODO: Invites
|
||||||
// TODO: a11y proper
|
// TODO: a11y proper
|
||||||
// TODO: Render more than bare minimum
|
// TODO: Render more than bare minimum
|
||||||
|
|
||||||
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
|
|
||||||
const isUnread = this.state.notificationState.color > NotificationColor.None;
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'mx_RoomTile': true,
|
'mx_RoomTile2': true,
|
||||||
// 'mx_RoomTile_selected': this.state.selected,
|
'mx_RoomTile2_selected': this.state.selected,
|
||||||
'mx_RoomTile_unread': isUnread,
|
|
||||||
'mx_RoomTile_unreadNotify': this.state.notificationState.color >= NotificationColor.Grey,
|
|
||||||
'mx_RoomTile_highlight': this.state.notificationState.color >= NotificationColor.Red,
|
|
||||||
'mx_RoomTile_invited': this.roomIsInvite,
|
|
||||||
// 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
|
|
||||||
'mx_RoomTile_noBadges': !hasBadge,
|
|
||||||
// 'mx_RoomTile_transparent': this.props.transparent,
|
|
||||||
// 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const avatarClasses = classNames({
|
|
||||||
'mx_RoomTile_avatar': true,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
let badge;
|
let badge;
|
||||||
|
const hasBadge = this.state.notificationState.color > NotificationColor.Bold;
|
||||||
if (hasBadge) {
|
if (hasBadge) {
|
||||||
|
const hasNotif = this.state.notificationState.color >= NotificationColor.Red;
|
||||||
|
const isEmptyBadge = !localStorage.getItem("mx_rl_rt_badgeCount");
|
||||||
const badgeClasses = classNames({
|
const badgeClasses = classNames({
|
||||||
'mx_RoomTile_badge': true,
|
'mx_RoomTile2_badge': true,
|
||||||
'mx_RoomTile_badgeButton': false, // this.state.badgeHover || isMenuDisplayed
|
'mx_RoomTile2_badgeHighlight': hasNotif,
|
||||||
|
'mx_RoomTile2_badgeEmpty': isEmptyBadge,
|
||||||
});
|
});
|
||||||
badge = <div className={badgeClasses}>{this.state.notificationState.symbol}</div>;
|
const symbol = this.state.notificationState.symbol;
|
||||||
|
badge = <div className={badgeClasses}>{isEmptyBadge ? null : symbol}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: the original RoomTile uses state for the room name. Do we need to?
|
// TODO: the original RoomTile uses state for the room name. Do we need to?
|
||||||
|
@ -226,20 +225,21 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
if (typeof name !== 'string') name = '';
|
if (typeof name !== 'string') name = '';
|
||||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||||
|
|
||||||
const nameClasses = classNames({
|
|
||||||
'mx_RoomTile_name': true,
|
|
||||||
'mx_RoomTile_invite': this.roomIsInvite,
|
|
||||||
'mx_RoomTile_badgeShown': hasBadge,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Support collapsed state properly
|
// TODO: Support collapsed state properly
|
||||||
let tooltip = null;
|
// TODO: Tooltip?
|
||||||
if (false) { // isCollapsed
|
|
||||||
if (this.state.hover) {
|
let messagePreview = null;
|
||||||
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} />
|
if (this.props.showMessagePreview) {
|
||||||
}
|
// TODO: Actually get the real message preview from state
|
||||||
|
messagePreview = <div className="mx_RoomTile2_messagePreview">I just ate a pie.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nameClasses = classNames({
|
||||||
|
"mx_RoomTile2_name": true,
|
||||||
|
"mx_RoomTile2_nameWithPreview": !!messagePreview,
|
||||||
|
});
|
||||||
|
|
||||||
|
const avatarSize = 32;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<RovingTabIndexWrapper inputRef={this.roomTile}>
|
<RovingTabIndexWrapper inputRef={this.roomTile}>
|
||||||
|
@ -254,20 +254,18 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
onClick={this.onTileClick}
|
onClick={this.onTileClick}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
>
|
>
|
||||||
<div className={avatarClasses}>
|
<div className="mx_RoomTile2_avatarContainer">
|
||||||
<div className="mx_RoomTile_avatar_container">
|
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize}/>
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomTile_nameContainer">
|
<div className="mx_RoomTile2_nameContainer">
|
||||||
<div className="mx_RoomTile_labelContainer">
|
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
|
||||||
<div title={name} className={nameClasses} tabIndex={-1} dir="auto">
|
{name}
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{messagePreview}
|
||||||
|
</div>
|
||||||
|
<div className="mx_RoomTile2_badgeContainer">
|
||||||
{badge}
|
{badge}
|
||||||
</div>
|
</div>
|
||||||
{tooltip}
|
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
}
|
}
|
||||||
</RovingTabIndexWrapper>
|
</RovingTabIndexWrapper>
|
||||||
|
|
|
@ -36,6 +36,7 @@ export enum Action {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the user settings. No additional payload information required.
|
* Open the user settings. No additional payload information required.
|
||||||
|
* Optionally can include an OpenToTabPayload.
|
||||||
*/
|
*/
|
||||||
ViewUserSettings = "view_user_settings",
|
ViewUserSettings = "view_user_settings",
|
||||||
|
|
||||||
|
@ -58,4 +59,9 @@ export enum Action {
|
||||||
* Focuses the user's cursor to the composer. No additional payload information required.
|
* Focuses the user's cursor to the composer. No additional payload information required.
|
||||||
*/
|
*/
|
||||||
FocusComposer = "focus_composer",
|
FocusComposer = "focus_composer",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the user menu (previously known as the top left menu). No additional payload information required.
|
||||||
|
*/
|
||||||
|
ToggleUserMenu = "toggle_user_menu",
|
||||||
}
|
}
|
||||||
|
|
27
src/dispatcher/payloads/OpenToTabPayload.ts
Normal file
27
src/dispatcher/payloads/OpenToTabPayload.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 { ActionPayload } from "../payloads";
|
||||||
|
import { Action } from "../actions";
|
||||||
|
|
||||||
|
export interface OpenToTabPayload extends ActionPayload {
|
||||||
|
action: Action.ViewUserSettings | string, // TODO: Add room settings action
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tab ID to open in the settings view to start, if possible.
|
||||||
|
*/
|
||||||
|
initialTabId?: string;
|
||||||
|
}
|
|
@ -1090,6 +1090,7 @@
|
||||||
"Low priority": "Low priority",
|
"Low priority": "Low priority",
|
||||||
"Historical": "Historical",
|
"Historical": "Historical",
|
||||||
"System Alerts": "System Alerts",
|
"System Alerts": "System Alerts",
|
||||||
|
"People": "People",
|
||||||
"This room": "This room",
|
"This room": "This room",
|
||||||
"Joining room …": "Joining room …",
|
"Joining room …": "Joining room …",
|
||||||
"Loading …": "Loading …",
|
"Loading …": "Loading …",
|
||||||
|
@ -1133,9 +1134,6 @@
|
||||||
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
|
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
|
||||||
"Not now": "Not now",
|
"Not now": "Not now",
|
||||||
"Don't ask me again": "Don't ask me again",
|
"Don't ask me again": "Don't ask me again",
|
||||||
"Jump to first unread room.": "Jump to first unread room.",
|
|
||||||
"Jump to first invite.": "Jump to first invite.",
|
|
||||||
"Add room": "Add room",
|
|
||||||
"Show %(n)s more": "Show %(n)s more",
|
"Show %(n)s more": "Show %(n)s more",
|
||||||
"Options": "Options",
|
"Options": "Options",
|
||||||
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
||||||
|
@ -2017,6 +2015,9 @@
|
||||||
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
"Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.",
|
||||||
"Active call": "Active call",
|
"Active call": "Active call",
|
||||||
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",
|
"There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",
|
||||||
|
"Jump to first unread room.": "Jump to first unread room.",
|
||||||
|
"Jump to first invite.": "Jump to first invite.",
|
||||||
|
"Add room": "Add room",
|
||||||
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
"You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?",
|
||||||
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
"You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?",
|
||||||
"Search failed": "Search failed",
|
"Search failed": "Search failed",
|
||||||
|
@ -2041,6 +2042,14 @@
|
||||||
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
|
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
|
||||||
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
|
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
|
||||||
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
||||||
|
"Switch to light mode": "Switch to light mode",
|
||||||
|
"Switch to dark mode": "Switch to dark mode",
|
||||||
|
"Switch theme": "Switch theme",
|
||||||
|
"Security & privacy": "Security & privacy",
|
||||||
|
"All settings": "All settings",
|
||||||
|
"Archived rooms": "Archived rooms",
|
||||||
|
"Feedback": "Feedback",
|
||||||
|
"Account settings": "Account settings",
|
||||||
"Could not load user profile": "Could not load user profile",
|
"Could not load user profile": "Could not load user profile",
|
||||||
"Verify this login": "Verify this login",
|
"Verify this login": "Verify this login",
|
||||||
"Session verified": "Session verified",
|
"Session verified": "Session verified",
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { TagID } from "./models";
|
import { TagID } from "./models";
|
||||||
|
|
||||||
const TILE_HEIGHT_PX = 34;
|
const TILE_HEIGHT_PX = 44;
|
||||||
|
|
||||||
interface ISerializedListLayout {
|
interface ISerializedListLayout {
|
||||||
numTiles: number;
|
numTiles: number;
|
||||||
|
|
|
@ -62,7 +62,7 @@ function setCustomThemeVars(customTheme) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCustomTheme(themeName) {
|
export function getCustomTheme(themeName) {
|
||||||
// set css variables
|
// set css variables
|
||||||
const customThemes = SettingsStore.getValue("custom_themes");
|
const customThemes = SettingsStore.getValue("custom_themes");
|
||||||
if (!customThemes) {
|
if (!customThemes) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue