Merge branch 'develop' into gsouquet/fix-18132
This commit is contained in:
commit
486d576b23
151 changed files with 2280 additions and 1058 deletions
|
@ -86,6 +86,7 @@
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
||||||
|
@import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss";
|
||||||
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
|
||||||
@import "./views/dialogs/_ModalWidgetDialog.scss";
|
@import "./views/dialogs/_ModalWidgetDialog.scss";
|
||||||
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
@import "./views/dialogs/_NewSessionReviewDialog.scss";
|
||||||
|
|
|
@ -234,6 +234,9 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing {
|
.mx_SpaceRoomView_landing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
> .mx_BaseAvatar_image,
|
> .mx_BaseAvatar_image,
|
||||||
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
@ -340,6 +343,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
|
flex: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceFeedbackPrompt {
|
.mx_SpaceFeedbackPrompt {
|
||||||
|
@ -350,6 +354,11 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_list {
|
||||||
|
// we don't want this container to get forced into the flexbox layout
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_privateScope {
|
.mx_SpaceRoomView_privateScope {
|
||||||
|
|
|
@ -27,7 +27,6 @@ limitations under the License.
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
line-height: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_initial {
|
.mx_BaseAvatar_initial {
|
||||||
|
|
|
@ -65,7 +65,7 @@ limitations under the License.
|
||||||
.mx_CreateRoomDialog_aliasContainer {
|
.mx_CreateRoomDialog_aliasContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
// put margin on container so it can collapse with siblings
|
// put margin on container so it can collapse with siblings
|
||||||
margin: 10px 0;
|
margin: 24px 0 10px;
|
||||||
|
|
||||||
.mx_RoomAliasField {
|
.mx_RoomAliasField {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -101,10 +101,6 @@ limitations under the License.
|
||||||
margin-left: 30px;
|
margin-left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CreateRoomDialog_topic {
|
|
||||||
margin-bottom: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_Dialog_content > .mx_SettingsFlag {
|
.mx_Dialog_content > .mx_SettingsFlag {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
@ -113,5 +109,56 @@ limitations under the License.
|
||||||
margin: 0 85px 0 0;
|
margin: 0 85px 0 0;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Dropdown {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: $font-family;
|
||||||
|
font-size: $font-14px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
.mx_Dropdown_input {
|
||||||
|
border: 1px solid $input-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dropdown_option {
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-32px;
|
||||||
|
height: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding-left: 30px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 6px;
|
||||||
|
top: 8px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateRoomDialog_dropdown_invite::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/lock.svg');
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateRoomDialog_dropdown_public::before {
|
||||||
|
mask-image: url('$(res)/img/globe.svg');
|
||||||
|
mask-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateRoomDialog_dropdown_restricted::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
150
res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss
Normal file
150
res/css/views/dialogs/_ManageRestrictedJoinRuleDialog.scss
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 0;
|
||||||
|
height: 60vh;
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
// To match the space around the title
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_noResults {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_section {
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_entry {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.mx_RoomAvatar_isSpaceRoom,
|
||||||
|
.mx_RoomAvatar_isSpaceRoom img {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_entry_name {
|
||||||
|
margin: 0 8px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: 30px;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_entry_description {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_section_spaces {
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_section_info {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 12px 0;
|
||||||
|
padding: 8px 8px 8px 42px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: calc(50% - 8px); // vertical centering
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_ManageRestrictedJoinRuleDialog_footer_buttons {
|
||||||
|
display: flex;
|
||||||
|
width: max-content;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
border: 1px solid $strong-input-border-color;
|
border: 1px solid $strong-input-border-color;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
@ -109,7 +109,7 @@ input.mx_Dropdown_option:focus {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
border: 1px solid $input-focused-border-color;
|
border: 1px solid $input-focused-border-color;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
|
|
|
@ -16,10 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
$timelineImageBorderRadius: 4px;
|
$timelineImageBorderRadius: 4px;
|
||||||
|
|
||||||
.mx_MImageBody {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MImageBody_thumbnail {
|
.mx_MImageBody_thumbnail {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border-radius: $timelineImageBorderRadius;
|
border-radius: $timelineImageBorderRadius;
|
||||||
|
@ -28,7 +24,7 @@ $timelineImageBorderRadius: 4px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
> canvas {
|
> div > canvas {
|
||||||
border-radius: $timelineImageBorderRadius;
|
border-radius: $timelineImageBorderRadius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,8 @@ limitations under the License.
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&.mx_EventTile_selected {
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -155,12 +156,24 @@ limitations under the License.
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
z-index: 9;
|
||||||
img {
|
img {
|
||||||
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
|
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_noSender {
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
top: -19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar,
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-has-reply=true] {
|
&[data-has-reply=true] {
|
||||||
> .mx_EventTile_line {
|
> .mx_EventTile_line {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -219,6 +232,8 @@ limitations under the License.
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
position: static;
|
position: static;
|
||||||
|
@ -289,7 +304,7 @@ limitations under the License.
|
||||||
& + .mx_EventListSummary {
|
& + .mx_EventListSummary {
|
||||||
.mx_EventTile {
|
.mx_EventTile {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding: 0;
|
padding: 2px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,8 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_EventTile_info .mx_EventTile_line {
|
&.mx_EventTile_info .mx_EventTile_line,
|
||||||
|
& ~ .mx_EventListSummary .mx_EventTile_avatar ~ .mx_EventTile_line {
|
||||||
padding-left: calc($left-gutter + 18px);
|
padding-left: calc($left-gutter + 18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ $left-gutter: 64px;
|
||||||
|
|
||||||
> .mx_EventTile_avatar {
|
> .mx_EventTile_avatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
|
|
|
@ -116,6 +116,11 @@ $irc-line-height: $font-18px;
|
||||||
.mx_EditMessageComposer_buttons {
|
.mx_EditMessageComposer_buttons {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ReactionsRow {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_emote {
|
.mx_EventTile_emote {
|
||||||
|
|
|
@ -34,7 +34,7 @@ limitations under the License.
|
||||||
.mx_LinkPreviewWidget_caption {
|
.mx_LinkPreviewWidget_caption {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-x: hidden; // cause it to wrap rather than clip
|
overflow: hidden; // cause it to wrap rather than clip
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LinkPreviewWidget_title {
|
.mx_LinkPreviewWidget_title {
|
||||||
|
|
|
@ -47,14 +47,14 @@ limitations under the License.
|
||||||
color: $settings-subsection-fg-color;
|
color: $settings-subsection-fg-color;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 10px 100px 10px 0; // Align with the rest of the view
|
margin: 10px 80px 10px 0; // Align with the rest of the view
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SettingsTab_section {
|
.mx_SettingsTab_section {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
|
||||||
.mx_SettingsFlag {
|
.mx_SettingsFlag {
|
||||||
margin-right: 100px;
|
margin-right: 80px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,44 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_SecurityRoomSettingsTab {
|
||||||
|
.mx_SettingsTab_showAdvanced {
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SecurityRoomSettingsTab_spacesWithAccess {
|
||||||
|
> h4 {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: 32px; // matches height of avatar for v-align
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
img.mx_RoomAvatar_isSpaceRoom,
|
||||||
|
.mx_RoomAvatar_isSpaceRoom img {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + span {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SecurityRoomSettingsTab_warning {
|
.mx_SecurityRoomSettingsTab_warning {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
@ -26,5 +64,51 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SecurityRoomSettingsTab_encryptionSection {
|
.mx_SecurityRoomSettingsTab_encryptionSection {
|
||||||
margin-bottom: 25px;
|
padding-bottom: 24px;
|
||||||
|
border-bottom: 1px solid $menu-border-color;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SecurityRoomSettingsTab_upgradeRequired {
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 4px 16px;
|
||||||
|
border: 1px solid $accent-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: $accent-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SecurityRoomSettingsTab_joinRule {
|
||||||
|
.mx_RadioButton {
|
||||||
|
padding-top: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.mx_RadioButton_content {
|
||||||
|
margin-left: 14px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 34px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
& + .mx_RadioButton {
|
||||||
|
border-top: 1px solid $menu-border-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g style="stroke:#454545;stroke-width:.8;fill:none;fill-rule:evenodd;stroke-linecap:round;stroke-linejoin:round" transform="translate(1 1)">
|
|
||||||
<circle cx="5" cy="5" r="5"/>
|
|
||||||
<path d="m0 5h10"/>
|
|
||||||
<path d="m5 0c1.25064019 1.36917645 1.96137638 3.14601693 2 5-.03862362 1.85398307-.74935981 3.63082355-2 5-1.25064019-1.36917645-1.96137638-3.14601693-2-5 .03862362-1.85398307.74935981-3.63082355 2-5z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 524 B |
|
@ -51,10 +51,15 @@ export async function startAnyRegistrationFlow(options) {
|
||||||
description: _t("Use your account or create a new one to continue."),
|
description: _t("Use your account or create a new one to continue."),
|
||||||
button: _t("Create Account"),
|
button: _t("Create Account"),
|
||||||
extraButtons: [
|
extraButtons: [
|
||||||
<button key="start_login" onClick={() => {
|
<button
|
||||||
|
key="start_login"
|
||||||
|
onClick={() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
|
||||||
}}>{ _t('Sign In') }</button>,
|
}}
|
||||||
|
>
|
||||||
|
{ _t('Sign In') }
|
||||||
|
</button>,
|
||||||
],
|
],
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (proceed) {
|
if (proceed) {
|
||||||
|
|
|
@ -34,7 +34,6 @@ import { getAddressType } from './UserAddress';
|
||||||
import { abbreviateUrl } from './utils/UrlUtils';
|
import { abbreviateUrl } from './utils/UrlUtils';
|
||||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
|
||||||
import { isPermalinkHost, parsePermalink } from "./utils/permalinks/Permalinks";
|
import { isPermalinkHost, parsePermalink } from "./utils/permalinks/Permalinks";
|
||||||
import { inviteUsersToRoom } from "./RoomInvite";
|
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
|
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
|
||||||
|
@ -49,6 +48,7 @@ import { UIFeature } from "./settings/UIFeature";
|
||||||
import { CHAT_EFFECTS } from "./effects";
|
import { CHAT_EFFECTS } from "./effects";
|
||||||
import CallHandler from "./CallHandler";
|
import CallHandler from "./CallHandler";
|
||||||
import { guessAndSetDMRoom } from "./Rooms";
|
import { guessAndSetDMRoom } from "./Rooms";
|
||||||
|
import { upgradeRoom } from './utils/RoomUpgrade';
|
||||||
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
|
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
|
||||||
import ErrorDialog from './components/views/dialogs/ErrorDialog';
|
import ErrorDialog from './components/views/dialogs/ErrorDialog';
|
||||||
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
|
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
|
||||||
|
@ -277,50 +277,8 @@ export const Commands = [
|
||||||
/*isPriority=*/false, /*isStatic=*/true);
|
/*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
|
||||||
return success(finished.then(async ([resp]) => {
|
return success(finished.then(async ([resp]) => {
|
||||||
if (!resp.continue) return;
|
if (!resp?.continue) return;
|
||||||
|
await upgradeRoom(room, args, resp.invite);
|
||||||
let checkForUpgradeFn;
|
|
||||||
try {
|
|
||||||
const upgradePromise = cli.upgradeRoom(roomId, args);
|
|
||||||
|
|
||||||
// We have to wait for the js-sdk to give us the room back so
|
|
||||||
// we can more effectively abuse the MultiInviter behaviour
|
|
||||||
// which heavily relies on the Room object being available.
|
|
||||||
if (resp.invite) {
|
|
||||||
checkForUpgradeFn = async (newRoom) => {
|
|
||||||
// The upgradePromise should be done by the time we await it here.
|
|
||||||
const { replacement_room: newRoomId } = await upgradePromise;
|
|
||||||
if (newRoom.roomId !== newRoomId) return;
|
|
||||||
|
|
||||||
const toInvite = [
|
|
||||||
...room.getMembersWithMembership("join"),
|
|
||||||
...room.getMembersWithMembership("invite"),
|
|
||||||
].map(m => m.userId).filter(m => m !== cli.getUserId());
|
|
||||||
|
|
||||||
if (toInvite.length > 0) {
|
|
||||||
// Errors are handled internally to this function
|
|
||||||
await inviteUsersToRoom(newRoomId, toInvite);
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.removeListener('Room', checkForUpgradeFn);
|
|
||||||
};
|
|
||||||
cli.on('Room', checkForUpgradeFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to await after so that the checkForUpgradesFn has a proper reference
|
|
||||||
// to the new room's ID.
|
|
||||||
await upgradePromise;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
|
|
||||||
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
|
|
||||||
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
|
|
||||||
title: _t('Error upgrading room'),
|
|
||||||
description: _t(
|
|
||||||
'Double check that your server supports the room version chosen and try again.'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
|
|
|
@ -269,7 +269,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>{ _t("Advanced") }</summary>
|
<summary>{ _t("Advanced") }</summary>
|
||||||
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick} >
|
<AccessibleButton kind='primary' onClick={this._onSkipPassPhraseClick}>
|
||||||
{ _t("Set up with a Security Key") }
|
{ _t("Set up with a Security Key") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
@ -474,7 +474,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
outlined
|
outlined
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup"></span>
|
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup" />
|
||||||
{ _t("Generate a Security Key") }
|
{ _t("Generate a Security Key") }
|
||||||
</div>
|
</div>
|
||||||
<div>{ _t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.") }</div>
|
<div>{ _t("We’ll generate a Security Key for you to store somewhere safe, like a password manager or a safe.") }</div>
|
||||||
|
@ -493,7 +493,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
outlined
|
outlined
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase"></span>
|
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase" />
|
||||||
{ _t("Enter a Security Phrase") }
|
{ _t("Enter a Security Phrase") }
|
||||||
</div>
|
</div>
|
||||||
<div>{ _t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.") }</div>
|
<div>{ _t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.") }</div>
|
||||||
|
@ -701,7 +701,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code>
|
<code ref={this._collectRecoveryKeyNode}>{ this._recoveryKey.encodedPrivateKey }</code>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
|
||||||
<AccessibleButton kind='primary' className="mx_Dialog_primary"
|
<AccessibleButton kind='primary'
|
||||||
|
className="mx_Dialog_primary"
|
||||||
onClick={this._onDownloadClick}
|
onClick={this._onDownloadClick}
|
||||||
disabled={this.state.phase === PHASE_STORING}
|
disabled={this.state.phase === PHASE_STORING}
|
||||||
>
|
>
|
||||||
|
|
|
@ -148,8 +148,12 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input ref={this._passphrase1} id='passphrase1'
|
<input
|
||||||
autoFocus={true} size='64' type='password'
|
ref={this._passphrase1}
|
||||||
|
id='passphrase1'
|
||||||
|
autoFocus={true}
|
||||||
|
size='64'
|
||||||
|
type='password'
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -161,8 +165,10 @@ export default class ExportE2eKeysDialog extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
<input ref={this._passphrase2} id='passphrase2'
|
<input ref={this._passphrase2}
|
||||||
size='64' type='password'
|
id='passphrase2'
|
||||||
|
size='64'
|
||||||
|
type='password'
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -174,7 +174,10 @@ export default class ImportE2eKeysDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value={_t('Import')}
|
<input
|
||||||
|
className='mx_Dialog_primary'
|
||||||
|
type='submit'
|
||||||
|
value={_t('Import')}
|
||||||
disabled={!this.state.enableSubmit || disableForm}
|
disabled={!this.state.enableSubmit || disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
|
|
|
@ -120,8 +120,7 @@ export default class EmbeddedPage extends React.PureComponent {
|
||||||
|
|
||||||
const content = <div className={`${className}_body`}
|
const content = <div className={`${className}_body`}
|
||||||
dangerouslySetInnerHTML={{ __html: this.state.page }}
|
dangerouslySetInnerHTML={{ __html: this.state.page }}
|
||||||
>
|
/>;
|
||||||
</div>;
|
|
||||||
|
|
||||||
if (this.props.scrollbar) {
|
if (this.props.scrollbar) {
|
||||||
return <AutoHideScrollbar className={classes}>
|
return <AutoHideScrollbar className={classes}>
|
||||||
|
|
|
@ -222,7 +222,7 @@ class FeaturedRoom extends React.Component {
|
||||||
|
|
||||||
let roomNameNode = null;
|
let roomNameNode = null;
|
||||||
if (permalink) {
|
if (permalink) {
|
||||||
roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>;
|
roomNameNode = <a href={permalink} onClick={this.onClick}>{ roomName }</a>;
|
||||||
} else {
|
} else {
|
||||||
roomNameNode = <span>{ roomName }</span>;
|
roomNameNode = <span>{ roomName }</span>;
|
||||||
}
|
}
|
||||||
|
@ -1185,10 +1185,13 @@ export default class GroupView extends React.Component {
|
||||||
avatarImage = <Spinner />;
|
avatarImage = <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
||||||
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
avatarImage = <GroupAvatar
|
||||||
|
groupId={this.props.groupId}
|
||||||
groupName={this.state.profileForm.name}
|
groupName={this.state.profileForm.name}
|
||||||
groupAvatarUrl={this.state.profileForm.avatar_url}
|
groupAvatarUrl={this.state.profileForm.avatar_url}
|
||||||
width={28} height={28} resizeMethod='crop'
|
width={28}
|
||||||
|
height={28}
|
||||||
|
resizeMethod='crop'
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1199,9 +1202,12 @@ export default class GroupView extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
<div className="mx_GroupView_avatarPicker_edit">
|
<div className="mx_GroupView_avatarPicker_edit">
|
||||||
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
|
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
|
||||||
<img src={require("../../../res/img/camera.svg")}
|
<img
|
||||||
alt={_t("Upload avatar")} title={_t("Upload avatar")}
|
src={require("../../../res/img/camera.svg")}
|
||||||
width="17" height="15" />
|
alt={_t("Upload avatar")}
|
||||||
|
title={_t("Upload avatar")}
|
||||||
|
width="17"
|
||||||
|
height="15" />
|
||||||
</label>
|
</label>
|
||||||
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
|
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1238,7 +1244,8 @@ export default class GroupView extends React.Component {
|
||||||
groupAvatarUrl={groupAvatarUrl}
|
groupAvatarUrl={groupAvatarUrl}
|
||||||
groupName={groupName}
|
groupName={groupName}
|
||||||
onClick={onGroupHeaderItemClick}
|
onClick={onGroupHeaderItemClick}
|
||||||
width={28} height={28}
|
width={28}
|
||||||
|
height={28}
|
||||||
/>;
|
/>;
|
||||||
if (summary.profile && summary.profile.name) {
|
if (summary.profile && summary.profile.name) {
|
||||||
nameNode = <div onClick={onGroupHeaderItemClick}>
|
nameNode = <div onClick={onGroupHeaderItemClick}>
|
||||||
|
@ -1269,28 +1276,32 @@ export default class GroupView extends React.Component {
|
||||||
key="_cancelButton"
|
key="_cancelButton"
|
||||||
onClick={this._onCancelClick}
|
onClick={this._onCancelClick}
|
||||||
>
|
>
|
||||||
<img src={require("../../../res/img/cancel.svg")} className="mx_filterFlipColor"
|
<img
|
||||||
width="18" height="18" alt={_t("Cancel")} />
|
src={require("../../../res/img/cancel.svg")}
|
||||||
|
className="mx_filterFlipColor"
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
alt={_t("Cancel")} />
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (summary.user && summary.user.membership === 'join') {
|
if (summary.user && summary.user.membership === 'join') {
|
||||||
rightButtons.push(
|
rightButtons.push(
|
||||||
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_editButton"
|
<AccessibleButton
|
||||||
|
className="mx_GroupHeader_button mx_GroupHeader_editButton"
|
||||||
key="_editButton"
|
key="_editButton"
|
||||||
onClick={this._onEditClick}
|
onClick={this._onEditClick}
|
||||||
title={_t("Community Settings")}
|
title={_t("Community Settings")}
|
||||||
>
|
/>,
|
||||||
</AccessibleButton>,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
rightButtons.push(
|
rightButtons.push(
|
||||||
<AccessibleButton className="mx_GroupHeader_button mx_GroupHeader_shareButton"
|
<AccessibleButton
|
||||||
|
className="mx_GroupHeader_button mx_GroupHeader_shareButton"
|
||||||
key="_shareButton"
|
key="_shareButton"
|
||||||
onClick={this._onShareClick}
|
onClick={this._onShareClick}
|
||||||
title={_t('Share Community')}
|
title={_t('Share Community')}
|
||||||
>
|
/>,
|
||||||
</AccessibleButton>,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,8 +109,7 @@ export default class MyGroups extends React.Component {
|
||||||
<SimpleRoomHeader title={_t("Communities")} icon={require("../../../res/img/icons-groups.svg")} />
|
<SimpleRoomHeader title={_t("Communities")} icon={require("../../../res/img/icons-groups.svg")} />
|
||||||
<div className='mx_MyGroups_header'>
|
<div className='mx_MyGroups_header'>
|
||||||
<div className="mx_MyGroups_headerCard">
|
<div className="mx_MyGroups_headerCard">
|
||||||
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick}>
|
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onCreateGroupClick} />
|
||||||
</AccessibleButton>
|
|
||||||
<div className="mx_MyGroups_headerCard_content">
|
<div className="mx_MyGroups_headerCard_content">
|
||||||
<div className="mx_MyGroups_headerCard_header">
|
<div className="mx_MyGroups_headerCard_header">
|
||||||
{ _t('Create a new community') }
|
{ _t('Create a new community') }
|
||||||
|
|
|
@ -266,8 +266,12 @@ export default class RoomStatusBar extends React.PureComponent {
|
||||||
<div className="mx_RoomStatusBar">
|
<div className="mx_RoomStatusBar">
|
||||||
<div role="alert">
|
<div role="alert">
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
<div className="mx_RoomStatusBar_connectionLostBar">
|
||||||
<img src={require("../../../res/img/feather-customised/warning-triangle.svg")} width="24"
|
<img
|
||||||
height="24" title="/!\ " alt="/!\ " />
|
src={require("../../../res/img/feather-customised/warning-triangle.svg")}
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
title="/!\ "
|
||||||
|
alt="/!\ " />
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||||
{ _t('Connectivity to the server has been lost.') }
|
{ _t('Connectivity to the server has been lost.') }
|
||||||
|
|
|
@ -1740,7 +1740,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
onJoinClick={this.onJoinButtonClicked}
|
onJoinClick={this.onJoinButtonClicked}
|
||||||
onForgetClick={this.onForgetClick}
|
onForgetClick={this.onForgetClick}
|
||||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||||
canPreview={false} error={this.state.roomLoadError}
|
canPreview={false}
|
||||||
|
error={this.state.roomLoadError}
|
||||||
roomAlias={roomAlias}
|
roomAlias={roomAlias}
|
||||||
joining={this.state.joining}
|
joining={this.state.joining}
|
||||||
inviterName={inviterName}
|
inviterName={inviterName}
|
||||||
|
|
|
@ -136,8 +136,8 @@ export default class SearchBox extends React.Component {
|
||||||
key="button"
|
key="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className="mx_SearchBox_closeButton"
|
className="mx_SearchBox_closeButton"
|
||||||
onClick={() => {this._clearSearch("button"); }}>
|
onClick={() => {this._clearSearch("button"); }}
|
||||||
</AccessibleButton>) : undefined;
|
/>) : undefined;
|
||||||
|
|
||||||
// show a shorter placeholder when blurred, if requested
|
// show a shorter placeholder when blurred, if requested
|
||||||
// this is used for the room filter field that has
|
// this is used for the room filter field that has
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { RefObject, useContext, useRef, useState } from "react";
|
import React, { RefObject, useContext, useRef, useState } from "react";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
import { Preset, JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { EventSubscription } from "fbemitter";
|
import { EventSubscription } from "fbemitter";
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ import Modal from "../../Modal";
|
||||||
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
|
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||||
import { JoinRule } from "../views/settings/tabs/room/SecurityRoomSettingsTab";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -101,7 +100,9 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
||||||
<hr />
|
<hr />
|
||||||
<div>
|
<div>
|
||||||
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
|
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
|
||||||
<AccessibleButton kind="link" onClick={() => {
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
if (onClick) onClick();
|
if (onClick) onClick();
|
||||||
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
||||||
featureId: "feature_spaces",
|
featureId: "feature_spaces",
|
||||||
|
@ -307,7 +308,6 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
|
||||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||||
|
|
||||||
let contextMenu;
|
let contextMenu;
|
||||||
|
@ -330,7 +330,7 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
if (await showCreateNewRoom(cli, space)) {
|
if (await showCreateNewRoom(space)) {
|
||||||
onNewRoomAdded();
|
onNewRoomAdded();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
@ -343,7 +343,7 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
const [added] = await showAddExistingRooms(cli, space);
|
const [added] = await showAddExistingRooms(space);
|
||||||
if (added) {
|
if (added) {
|
||||||
onNewRoomAdded();
|
onNewRoomAdded();
|
||||||
}
|
}
|
||||||
|
@ -397,11 +397,11 @@ const SpaceLanding = ({ space }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let settingsButton;
|
let settingsButton;
|
||||||
if (shouldShowSpaceSettings(cli, space)) {
|
if (shouldShowSpaceSettings(space)) {
|
||||||
settingsButton = <AccessibleTooltipButton
|
settingsButton = <AccessibleTooltipButton
|
||||||
className="mx_SpaceRoomView_landing_settingsButton"
|
className="mx_SpaceRoomView_landing_settingsButton"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
showSpaceSettings(cli, space);
|
showSpaceSettings(space);
|
||||||
}}
|
}}
|
||||||
title={_t("Settings")}
|
title={_t("Settings")}
|
||||||
/>;
|
/>;
|
||||||
|
@ -553,9 +553,7 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons" />
|
||||||
|
|
||||||
</div>
|
|
||||||
<SpaceFeedbackPrompt />
|
<SpaceFeedbackPrompt />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -315,7 +315,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the " +
|
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the " +
|
||||||
"link it contains, click below.", { emailAddress: this.state.email }) }
|
"link it contains, click below.", { emailAddress: this.state.email }) }
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
<input
|
||||||
|
className="mx_Login_submit"
|
||||||
|
type="button"
|
||||||
|
onClick={this.onVerify}
|
||||||
value={_t('I have verified my email address')} />
|
value={_t('I have verified my email address')} />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -328,7 +331,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
"push notifications. To re-enable notifications, sign in again on each " +
|
"push notifications. To re-enable notifications, sign in again on each " +
|
||||||
"device.",
|
"device.",
|
||||||
) }</p>
|
) }</p>
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
<input
|
||||||
|
className="mx_Login_submit"
|
||||||
|
type="button"
|
||||||
|
onClick={this.props.onComplete}
|
||||||
value={_t('Return to login screen')} />
|
value={_t('Return to login screen')} />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,7 +463,9 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
"Either use HTTPS or <a>enable unsafe scripts</a>.", {},
|
"Either use HTTPS or <a>enable unsafe scripts</a>.", {},
|
||||||
{
|
{
|
||||||
'a': (sub) => {
|
'a': (sub) => {
|
||||||
return <a target="_blank" rel="noreferrer noopener"
|
return <a
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
|
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
|
||||||
>
|
>
|
||||||
{ sub }
|
{ sub }
|
||||||
|
|
|
@ -557,12 +557,16 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
loggedInUserId: this.state.differentLoggedInUserId,
|
loggedInUserId: this.state.differentLoggedInUserId,
|
||||||
},
|
},
|
||||||
) }</p>
|
) }</p>
|
||||||
<p><AccessibleButton element="span" className="mx_linkButton" onClick={async event => {
|
<p><AccessibleButton
|
||||||
|
element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={async event => {
|
||||||
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
const sessionLoaded = await this.onLoginClickWithCheck(event);
|
||||||
if (sessionLoaded) {
|
if (sessionLoaded) {
|
||||||
dis.dispatch({ action: "view_welcome_page" });
|
dis.dispatch({ action: "view_welcome_page" });
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{ _t("Continue with previous account") }
|
{ _t("Continue with previous account") }
|
||||||
</AccessibleButton></p>
|
</AccessibleButton></p>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
|
||||||
import React, { createRef, ReactNode, RefObject } from "react";
|
import React, { createRef, ReactNode, RefObject } from "react";
|
||||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
|
||||||
import PlayPauseButton from "./PlayPauseButton";
|
import PlayPauseButton from "./PlayPauseButton";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { formatBytes } from "../../../utils/FormattingUtils";
|
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||||
|
@ -25,47 +23,13 @@ import { Key } from "../../../Keyboard";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import SeekBar from "./SeekBar";
|
import SeekBar from "./SeekBar";
|
||||||
import PlaybackClock from "./PlaybackClock";
|
import PlaybackClock from "./PlaybackClock";
|
||||||
|
import AudioPlayerBase from "./AudioPlayerBase";
|
||||||
interface IProps {
|
|
||||||
// Playback instance to render. Cannot change during component lifecycle: create
|
|
||||||
// an all-new component instead.
|
|
||||||
playback: Playback;
|
|
||||||
|
|
||||||
mediaName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
playbackPhase: PlaybackState;
|
|
||||||
error?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||||
export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
export default class AudioPlayer extends AudioPlayerBase {
|
||||||
private playPauseRef: RefObject<PlayPauseButton> = createRef();
|
private playPauseRef: RefObject<PlayPauseButton> = createRef();
|
||||||
private seekRef: RefObject<SeekBar> = createRef();
|
private seekRef: RefObject<SeekBar> = createRef();
|
||||||
|
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
playbackPhase: PlaybackState.Decoding, // default assumption
|
|
||||||
};
|
|
||||||
|
|
||||||
// We don't need to de-register: the class handles this for us internally
|
|
||||||
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
|
|
||||||
|
|
||||||
// Don't wait for the promise to complete - it will emit a progress update when it
|
|
||||||
// is done, and it's not meant to take long anyhow.
|
|
||||||
this.props.playback.prepare().catch(e => {
|
|
||||||
console.error("Error processing audio file:", e);
|
|
||||||
this.setState({ error: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPlaybackUpdate = (ev: PlaybackState) => {
|
|
||||||
this.setState({ playbackPhase: ev });
|
|
||||||
};
|
|
||||||
|
|
||||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
// stopPropagation() prevents the FocusComposer catch-all from triggering,
|
// stopPropagation() prevents the FocusComposer catch-all from triggering,
|
||||||
// but we need to do it on key down instead of press (even though the user
|
// but we need to do it on key down instead of press (even though the user
|
||||||
|
@ -91,10 +55,10 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||||
return `(${formatBytes(bytes)})`;
|
return `(${formatBytes(bytes)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): ReactNode {
|
protected renderComponent(): ReactNode {
|
||||||
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
||||||
// events for accessibility
|
// events for accessibility
|
||||||
return <>
|
return (
|
||||||
<div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
<div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
||||||
<div className='mx_AudioPlayer_primaryContainer'>
|
<div className='mx_AudioPlayer_primaryContainer'>
|
||||||
<PlayPauseButton
|
<PlayPauseButton
|
||||||
|
@ -124,7 +88,6 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||||
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
);
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal file
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||||
|
import { TileShape } from "../rooms/EventTile";
|
||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// Playback instance to render. Cannot change during component lifecycle: create
|
||||||
|
// an all-new component instead.
|
||||||
|
playback: Playback;
|
||||||
|
|
||||||
|
mediaName?: string;
|
||||||
|
tileShape?: TileShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
playbackPhase: PlaybackState;
|
||||||
|
error?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.audio_messages.AudioPlayerBase")
|
||||||
|
export default abstract class AudioPlayerBase extends React.PureComponent<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
playbackPhase: PlaybackState.Decoding, // default assumption
|
||||||
|
};
|
||||||
|
|
||||||
|
// We don't need to de-register: the class handles this for us internally
|
||||||
|
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
|
||||||
|
|
||||||
|
// Don't wait for the promise to complete - it will emit a progress update when it
|
||||||
|
// is done, and it's not meant to take long anyhow.
|
||||||
|
this.props.playback.prepare().catch(e => {
|
||||||
|
console.error("Error processing audio file:", e);
|
||||||
|
this.setState({ error: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlaybackUpdate = (ev: PlaybackState) => {
|
||||||
|
this.setState({ playbackPhase: ev });
|
||||||
|
};
|
||||||
|
|
||||||
|
protected abstract renderComponent(): ReactNode;
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
return <>
|
||||||
|
{ this.renderComponent() }
|
||||||
|
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import Clock from "./Clock";
|
import Clock from "./Clock";
|
||||||
import { Playback } from "../../../voice/Playback";
|
import { Playback } from "../../../audio/Playback";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
playback: Playback;
|
playback: Playback;
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording";
|
import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import Clock from "./Clock";
|
import Clock from "./Clock";
|
||||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../voice/VoiceRecording";
|
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { arrayFastResample } from "../../../utils/arrays";
|
import { arrayFastResample } from "../../../utils/arrays";
|
||||||
import { percentageOf } from "../../../utils/numbers";
|
import { percentageOf } from "../../../utils/numbers";
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React, { ReactNode } from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
// omitted props are handled by render function
|
// omitted props are handled by render function
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import Clock from "./Clock";
|
import Clock from "./Clock";
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
||||||
import Waveform from "./Waveform";
|
import Waveform from "./Waveform";
|
||||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../voice/Playback";
|
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
|
||||||
import { percentageOf } from "../../../utils/numbers";
|
import { percentageOf } from "../../../utils/numbers";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
|
|
@ -14,68 +14,30 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
|
||||||
import PlayPauseButton from "./PlayPauseButton";
|
import PlayPauseButton from "./PlayPauseButton";
|
||||||
import PlaybackClock from "./PlaybackClock";
|
import PlaybackClock from "./PlaybackClock";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { TileShape } from "../rooms/EventTile";
|
import { TileShape } from "../rooms/EventTile";
|
||||||
import PlaybackWaveform from "./PlaybackWaveform";
|
import PlaybackWaveform from "./PlaybackWaveform";
|
||||||
import { _t } from "../../../languageHandler";
|
import AudioPlayerBase from "./AudioPlayerBase";
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
// Playback instance to render. Cannot change during component lifecycle: create
|
|
||||||
// an all-new component instead.
|
|
||||||
playback: Playback;
|
|
||||||
|
|
||||||
tileShape?: TileShape;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
playbackPhase: PlaybackState;
|
|
||||||
error?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||||
export default class RecordingPlayback extends React.PureComponent<IProps, IState> {
|
export default class RecordingPlayback extends AudioPlayerBase {
|
||||||
constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
playbackPhase: PlaybackState.Decoding, // default assumption
|
|
||||||
};
|
|
||||||
|
|
||||||
// We don't need to de-register: the class handles this for us internally
|
|
||||||
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
|
|
||||||
|
|
||||||
// Don't wait for the promise to complete - it will emit a progress update when it
|
|
||||||
// is done, and it's not meant to take long anyhow.
|
|
||||||
this.props.playback.prepare().catch(e => {
|
|
||||||
console.error("Error processing audio file:", e);
|
|
||||||
this.setState({ error: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private get isWaveformable(): boolean {
|
private get isWaveformable(): boolean {
|
||||||
return this.props.tileShape !== TileShape.Notif
|
return this.props.tileShape !== TileShape.Notif
|
||||||
&& this.props.tileShape !== TileShape.FileGrid
|
&& this.props.tileShape !== TileShape.FileGrid
|
||||||
&& this.props.tileShape !== TileShape.Pinned;
|
&& this.props.tileShape !== TileShape.Pinned;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPlaybackUpdate = (ev: PlaybackState) => {
|
protected renderComponent(): ReactNode {
|
||||||
this.setState({ playbackPhase: ev });
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): ReactNode {
|
|
||||||
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
|
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
|
||||||
return <>
|
return (
|
||||||
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
||||||
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
||||||
<PlaybackClock playback={this.props.playback} />
|
<PlaybackClock playback={this.props.playback} />
|
||||||
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
||||||
</div>
|
</div>
|
||||||
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
);
|
||||||
</>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
|
|
|
@ -54,9 +54,13 @@ export default class Waveform extends React.PureComponent<IProps, IState> {
|
||||||
'mx_Waveform_bar': true,
|
'mx_Waveform_bar': true,
|
||||||
'mx_Waveform_bar_100pct': isCompleteBar,
|
'mx_Waveform_bar_100pct': isCompleteBar,
|
||||||
});
|
});
|
||||||
return <span key={i} style={{
|
return <span
|
||||||
|
key={i}
|
||||||
|
style={{
|
||||||
"--barHeight": h,
|
"--barHeight": h,
|
||||||
} as WaveformCSSProperties} className={classes} />;
|
} as WaveformCSSProperties}
|
||||||
|
className={classes}
|
||||||
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -416,8 +416,10 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||||
let submitButton;
|
let submitButton;
|
||||||
if (this.props.showContinue !== false) {
|
if (this.props.showContinue !== false) {
|
||||||
// XXX: button classes
|
// XXX: button classes
|
||||||
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
submitButton = <button
|
||||||
onClick={this.trySubmit} disabled={!allChecked}>{ _t("Accept") }</button>;
|
className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||||
|
onClick={this.trySubmit}
|
||||||
|
disabled={!allChecked}>{ _t("Accept") }</button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -616,7 +618,9 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
|
||||||
aria-label={_t("Code")}
|
aria-label={_t("Code")}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<input type="submit" value={_t("Submit")}
|
<input
|
||||||
|
type="submit"
|
||||||
|
value={_t("Submit")}
|
||||||
className={submitClasses}
|
className={submitClasses}
|
||||||
disabled={!enableSubmit}
|
disabled={!enableSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -187,7 +187,8 @@ const BaseAvatar = (props: IProps) => {
|
||||||
width: toPx(width),
|
width: toPx(width),
|
||||||
height: toPx(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
title={title} alt={_t("Avatar")}
|
title={title}
|
||||||
|
alt={_t("Avatar")}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
|
@ -201,7 +202,8 @@ const BaseAvatar = (props: IProps) => {
|
||||||
width: toPx(width),
|
width: toPx(width),
|
||||||
height: toPx(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
title={title} alt=""
|
title={title}
|
||||||
|
alt=""
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -102,8 +102,12 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title}
|
<BaseAvatar {...otherProps}
|
||||||
idName={userId} url={this.state.imageUrl} onClick={onClick} />
|
name={this.state.name}
|
||||||
|
title={this.state.title}
|
||||||
|
idName={userId}
|
||||||
|
url={this.state.imageUrl}
|
||||||
|
onClick={onClick} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ComponentProps } from 'react';
|
import React, { ComponentProps } from 'react';
|
||||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
|
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import BaseAvatar from './BaseAvatar';
|
import BaseAvatar from './BaseAvatar';
|
||||||
import ImageView from '../elements/ImageView';
|
import ImageView from '../elements/ImageView';
|
||||||
|
@ -32,11 +34,14 @@ interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idNam
|
||||||
// oobData.avatarUrl should be set (else there
|
// oobData.avatarUrl should be set (else there
|
||||||
// would be nowhere to get the avatar from)
|
// would be nowhere to get the avatar from)
|
||||||
room?: Room;
|
room?: Room;
|
||||||
oobData?: IOOBData;
|
oobData?: IOOBData & {
|
||||||
|
roomId?: string;
|
||||||
|
};
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
resizeMethod?: ResizeMethod;
|
resizeMethod?: ResizeMethod;
|
||||||
viewAvatarOnClick?: boolean;
|
viewAvatarOnClick?: boolean;
|
||||||
|
className?: string;
|
||||||
onClick?(): void;
|
onClick?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,15 +134,19 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { room, oobData, viewAvatarOnClick, onClick, ...otherProps } = this.props;
|
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
|
||||||
|
|
||||||
const roomName = room ? room.name : oobData.name;
|
const roomName = room ? room.name : oobData.name;
|
||||||
// If the room is a DM, we use the other user's ID for the color hash
|
// If the room is a DM, we use the other user's ID for the color hash
|
||||||
// in order to match the room avatar with their avatar
|
// in order to match the room avatar with their avatar
|
||||||
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : null;
|
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : oobData.roomId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar {...otherProps}
|
<BaseAvatar
|
||||||
|
{...otherProps}
|
||||||
|
className={classNames(className, {
|
||||||
|
mx_RoomAvatar_isSpaceRoom: room?.isSpaceRoom(),
|
||||||
|
})}
|
||||||
name={roomName}
|
name={roomName}
|
||||||
idName={idName}
|
idName={idName}
|
||||||
urls={this.state.urls}
|
urls={this.state.urls}
|
||||||
|
|
|
@ -60,8 +60,10 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
|
||||||
<AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} />
|
<AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_DialPadContextMenu_header">
|
<div className="mx_DialPadContextMenu_header">
|
||||||
<Field className="mx_DialPadContextMenu_dialled"
|
<Field
|
||||||
value={this.state.value} autoFocus={true}
|
className="mx_DialPadContextMenu_dialled"
|
||||||
|
value={this.state.value}
|
||||||
|
autoFocus={true}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -109,8 +109,10 @@ export default class StatusMessageContextMenu extends React.Component {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
|
actionButton = <AccessibleButton
|
||||||
disabled={!this.state.message} onClick={this._onSubmit}
|
className="mx_StatusMessageContextMenu_submit"
|
||||||
|
disabled={!this.state.message}
|
||||||
|
onClick={this._onSubmit}
|
||||||
>
|
>
|
||||||
<span>{ _t("Set status") }</span>
|
<span>{ _t("Set status") }</span>
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
|
@ -121,12 +123,19 @@ export default class StatusMessageContextMenu extends React.Component {
|
||||||
spinner = <Spinner w="24" h="24" />;
|
spinner = <Spinner w="24" h="24" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = <form className="mx_StatusMessageContextMenu_form"
|
const form = <form
|
||||||
autoComplete="off" onSubmit={this._onSubmit}
|
className="mx_StatusMessageContextMenu_form"
|
||||||
|
autoComplete="off"
|
||||||
|
onSubmit={this._onSubmit}
|
||||||
>
|
>
|
||||||
<input type="text" className="mx_StatusMessageContextMenu_message"
|
<input
|
||||||
key="message" placeholder={_t("Set a new status...")}
|
type="text"
|
||||||
autoFocus={true} maxLength="60" value={this.state.message}
|
className="mx_StatusMessageContextMenu_message"
|
||||||
|
key="message"
|
||||||
|
placeholder={_t("Set a new status...")}
|
||||||
|
autoFocus={true}
|
||||||
|
maxLength="60"
|
||||||
|
value={this.state.message}
|
||||||
onChange={this._onStatusChange}
|
onChange={this._onStatusChange}
|
||||||
/>
|
/>
|
||||||
<div className="mx_StatusMessageContextMenu_actionContainer">
|
<div className="mx_StatusMessageContextMenu_actionContainer">
|
||||||
|
|
|
@ -76,7 +76,8 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
streamAudioStreamButton = <IconizedContextMenuOption
|
streamAudioStreamButton = <IconizedContextMenuOption
|
||||||
onClick={onStreamAudioClick} label={_t("Start audio stream")}
|
onClick={onStreamAudioClick}
|
||||||
|
label={_t("Start audio stream")}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
import React, { ReactNode, useContext, useMemo, useState } from "react";
|
import React, { ReactNode, useContext, useMemo, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
@ -44,9 +43,8 @@ import EntityTile from "../rooms/EntityTile";
|
||||||
import BaseAvatar from "../avatars/BaseAvatar";
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
matrixClient: MatrixClient;
|
|
||||||
space: Room;
|
space: Room;
|
||||||
onCreateRoomClick(cli: MatrixClient, space: Room): void;
|
onCreateRoomClick(space: Room): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Entry = ({ room, checked, onChange }) => {
|
const Entry = ({ room, checked, onChange }) => {
|
||||||
|
@ -211,10 +209,16 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
function overflowTile(overflowCount, totalCount) {
|
function overflowTile(overflowCount, totalCount) {
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||||
return (
|
return (
|
||||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
<EntityTile
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={() => setTruncateAt(totalCount)} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={() => setTruncateAt(totalCount)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +299,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onFinished }) => {
|
||||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
||||||
|
|
||||||
|
@ -344,13 +348,13 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
>
|
>
|
||||||
<MatrixClientContext.Provider value={cli}>
|
<MatrixClientContext.Provider value={space.client}>
|
||||||
<AddExistingToSpace
|
<AddExistingToSpace
|
||||||
space={space}
|
space={space}
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
footerPrompt={<>
|
footerPrompt={<>
|
||||||
<div>{ _t("Want to add a new room instead?") }</div>
|
<div>{ _t("Want to add a new room instead?") }</div>
|
||||||
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
<AccessibleButton onClick={() => onCreateRoomClick(space)} kind="link">
|
||||||
{ _t("Create a new room") }
|
{ _t("Create a new room") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</>}
|
</>}
|
||||||
|
|
|
@ -665,8 +665,8 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
|
||||||
onChange={this.onQueryChanged}
|
onChange={this.onQueryChanged}
|
||||||
placeholder={this.getPlaceholder()}
|
placeholder={this.getPlaceholder()}
|
||||||
defaultValue={this.props.value}
|
defaultValue={this.props.value}
|
||||||
autoFocus={this.props.focus}>
|
autoFocus={this.props.focus}
|
||||||
</textarea>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredSuggestedList = this.getFilteredSuggestions();
|
const filteredSuggestedList = this.getFilteredSuggestions();
|
||||||
|
@ -727,8 +727,12 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
|
<BaseDialog
|
||||||
onFinished={this.props.onFinished} title={this.props.title}>
|
className="mx_AddressPickerDialog"
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={this.props.title}
|
||||||
|
>
|
||||||
{ inputLabel }
|
{ inputLabel }
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
|
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
|
||||||
|
|
|
@ -118,9 +118,7 @@ export default class BaseDialog extends React.Component {
|
||||||
|
|
||||||
let headerImage;
|
let headerImage;
|
||||||
if (this.props.headerImage) {
|
if (this.props.headerImage) {
|
||||||
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage}
|
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} alt="" />;
|
||||||
alt=""
|
|
||||||
/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -71,13 +71,16 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
|
|
||||||
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
|
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
|
||||||
|
|
||||||
<AccessibleButton kind="link" onClick={() => {
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
onFinished(false);
|
onFinished(false);
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: UserTab.Labs,
|
initialTabId: UserTab.Labs,
|
||||||
});
|
});
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{ _t("To leave the beta, visit your settings.") }
|
{ _t("To leave the beta, visit your settings.") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -188,7 +188,9 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_BugReportDialog" onFinished={this.onCancel}
|
<BaseDialog
|
||||||
|
className="mx_BugReportDialog"
|
||||||
|
onFinished={this.onCancel}
|
||||||
title={_t('Submit debug logs')}
|
title={_t('Submit debug logs')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
|
|
|
@ -205,9 +205,12 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
||||||
people.push((
|
people.push((
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this.onShowMorePeople}
|
onClick={this.onShowMorePeople}
|
||||||
kind="link" key="more"
|
kind="link"
|
||||||
|
key="more"
|
||||||
className="mx_CommunityPrototypeInviteDialog_morePeople"
|
className="mx_CommunityPrototypeInviteDialog_morePeople"
|
||||||
>{ _t("Show more") }</AccessibleButton>
|
>
|
||||||
|
{ _t("Show more") }
|
||||||
|
</AccessibleButton>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,10 +243,13 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
||||||
{ peopleIntro }
|
{ peopleIntro }
|
||||||
{ people }
|
{ people }
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary" onClick={this.onSubmit}
|
kind="primary"
|
||||||
|
onClick={this.onSubmit}
|
||||||
disabled={this.state.busy}
|
disabled={this.state.busy}
|
||||||
className="mx_CommunityPrototypeInviteDialog_primaryButton"
|
className="mx_CommunityPrototypeInviteDialog_primaryButton"
|
||||||
>{ buttonText }</AccessibleButton>
|
>
|
||||||
|
{ buttonText }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -37,8 +37,8 @@ export default class ConfirmRedactDialog extends React.Component<IProps> {
|
||||||
"Note that if you delete a room name or topic change, it could undo the change.")}
|
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||||
placeholder={_t("Reason (optional)")}
|
placeholder={_t("Reason (optional)")}
|
||||||
focus
|
focus
|
||||||
button={_t("Remove")}>
|
button={_t("Remove")}
|
||||||
</TextInputDialog>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,9 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_ConfirmUserActionDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
|
|
|
@ -204,8 +204,10 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
|
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
|
||||||
<input
|
<input
|
||||||
type="file" style={{ display: "none" }}
|
type="file"
|
||||||
ref={this.avatarUploadRef} accept="image/*"
|
style={{ display: "none" }}
|
||||||
|
ref={this.avatarUploadRef}
|
||||||
|
accept="image/*"
|
||||||
onChange={this.onAvatarChanged}
|
onChange={this.onAvatarChanged}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -123,7 +123,9 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_CreateGroupDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={_t('Create Community')}
|
title={_t('Create Community')}
|
||||||
>
|
>
|
||||||
<form onSubmit={this.onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
|
@ -133,8 +135,11 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
||||||
<label htmlFor="groupname">{ _t('Community Name') }</label>
|
<label htmlFor="groupname">{ _t('Community Name') }</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="groupname" className="mx_CreateGroupDialog_input"
|
<input
|
||||||
autoFocus={true} size={64}
|
id="groupname"
|
||||||
|
className="mx_CreateGroupDialog_input"
|
||||||
|
autoFocus={true}
|
||||||
|
size={64}
|
||||||
placeholder={_t('Example')}
|
placeholder={_t('Example')}
|
||||||
onChange={this.onGroupNameChange}
|
onChange={this.onGroupNameChange}
|
||||||
value={this.state.groupName}
|
value={this.state.groupName}
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { ChangeEvent, createRef, KeyboardEvent, SyntheticEvent } from "react";
|
import React, { ChangeEvent, createRef, KeyboardEvent, SyntheticEvent } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { JoinRule, Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import withValidation, { IFieldState } from '../elements/Validation';
|
import withValidation, { IFieldState } from '../elements/Validation';
|
||||||
|
@ -31,7 +32,8 @@ import RoomAliasField from "../elements/RoomAliasField";
|
||||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import BaseDialog from "../dialogs/BaseDialog";
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
import { Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
|
import Dropdown from "../elements/Dropdown";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
defaultPublic?: boolean;
|
defaultPublic?: boolean;
|
||||||
|
@ -41,7 +43,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
isPublic: boolean;
|
joinRule: JoinRule;
|
||||||
isEncrypted: boolean;
|
isEncrypted: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
topic: string;
|
topic: string;
|
||||||
|
@ -54,15 +56,25 @@ interface IState {
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.CreateRoomDialog")
|
@replaceableComponent("views.dialogs.CreateRoomDialog")
|
||||||
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
|
private readonly supportsRestricted: boolean;
|
||||||
private nameField = createRef<Field>();
|
private nameField = createRef<Field>();
|
||||||
private aliasField = createRef<RoomAliasField>();
|
private aliasField = createRef<RoomAliasField>();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.supportsRestricted = this.props.parentSpace && !!SpaceStore.instance.restrictedJoinRuleSupport?.preferred;
|
||||||
|
|
||||||
|
let joinRule = JoinRule.Invite;
|
||||||
|
if (this.props.defaultPublic) {
|
||||||
|
joinRule = JoinRule.Public;
|
||||||
|
} else if (this.supportsRestricted) {
|
||||||
|
joinRule = JoinRule.Restricted;
|
||||||
|
}
|
||||||
|
|
||||||
const config = SdkConfig.get();
|
const config = SdkConfig.get();
|
||||||
this.state = {
|
this.state = {
|
||||||
isPublic: this.props.defaultPublic || false,
|
joinRule,
|
||||||
isEncrypted: privateShouldBeEncrypted(),
|
isEncrypted: privateShouldBeEncrypted(),
|
||||||
name: this.props.defaultName || "",
|
name: this.props.defaultName || "",
|
||||||
topic: "",
|
topic: "",
|
||||||
|
@ -81,13 +93,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
const opts: IOpts = {};
|
const opts: IOpts = {};
|
||||||
const createOpts: IOpts["createOpts"] = opts.createOpts = {};
|
const createOpts: IOpts["createOpts"] = opts.createOpts = {};
|
||||||
createOpts.name = this.state.name;
|
createOpts.name = this.state.name;
|
||||||
if (this.state.isPublic) {
|
|
||||||
|
if (this.state.joinRule === JoinRule.Public) {
|
||||||
createOpts.visibility = Visibility.Public;
|
createOpts.visibility = Visibility.Public;
|
||||||
createOpts.preset = Preset.PublicChat;
|
createOpts.preset = Preset.PublicChat;
|
||||||
opts.guestAccess = false;
|
opts.guestAccess = false;
|
||||||
const { alias } = this.state;
|
const { alias } = this.state;
|
||||||
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
||||||
|
} else {
|
||||||
|
// If we cannot change encryption we pass `true` for safety, the server should automatically do this for us.
|
||||||
|
opts.encryption = this.state.canChangeEncryption ? this.state.isEncrypted : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.topic) {
|
if (this.state.topic) {
|
||||||
createOpts.topic = this.state.topic;
|
createOpts.topic = this.state.topic;
|
||||||
}
|
}
|
||||||
|
@ -95,22 +112,13 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
createOpts.creation_content = { 'm.federate': false };
|
createOpts.creation_content = { 'm.federate': false };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.isPublic) {
|
|
||||||
if (this.state.canChangeEncryption) {
|
|
||||||
opts.encryption = this.state.isEncrypted;
|
|
||||||
} else {
|
|
||||||
// the server should automatically do this for us, but for safety
|
|
||||||
// we'll demand it too.
|
|
||||||
opts.encryption = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.parentSpace) {
|
if (this.props.parentSpace && this.state.joinRule === JoinRule.Restricted) {
|
||||||
opts.parentSpace = this.props.parentSpace;
|
opts.parentSpace = this.props.parentSpace;
|
||||||
|
opts.joinRule = JoinRule.Restricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
|
@ -172,8 +180,8 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
this.setState({ topic: ev.target.value });
|
this.setState({ topic: ev.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPublicChange = (isPublic: boolean) => {
|
private onJoinRuleChange = (joinRule: JoinRule) => {
|
||||||
this.setState({ isPublic });
|
this.setState({ joinRule });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onEncryptedChange = (isEncrypted: boolean) => {
|
private onEncryptedChange = (isEncrypted: boolean) => {
|
||||||
|
@ -210,7 +218,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let aliasField;
|
let aliasField;
|
||||||
if (this.state.isPublic) {
|
if (this.state.joinRule === JoinRule.Public) {
|
||||||
const domain = MatrixClientPeg.get().getDomain();
|
const domain = MatrixClientPeg.get().getDomain();
|
||||||
aliasField = (
|
aliasField = (
|
||||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||||
|
@ -224,19 +232,46 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let publicPrivateLabel = <p>{ _t(
|
let publicPrivateLabel: JSX.Element;
|
||||||
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
|
||||||
"found and joined by anyone.",
|
|
||||||
) }</p>;
|
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
publicPrivateLabel = <p>{ _t(
|
publicPrivateLabel = <p>
|
||||||
|
{ _t(
|
||||||
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
||||||
"found and joined by anyone in this community.",
|
"found and joined by anyone in this community.",
|
||||||
) }</p>;
|
) }
|
||||||
|
</p>;
|
||||||
|
} else if (this.state.joinRule === JoinRule.Restricted) {
|
||||||
|
publicPrivateLabel = <p>
|
||||||
|
{ _t(
|
||||||
|
"Everyone in <SpaceName/> will be able to find and join this room.", {}, {
|
||||||
|
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
|
||||||
|
{ _t("You can change this at any time from room settings.") }
|
||||||
|
</p>;
|
||||||
|
} else if (this.state.joinRule === JoinRule.Public) {
|
||||||
|
publicPrivateLabel = <p>
|
||||||
|
{ _t(
|
||||||
|
"Anyone will be able to find and join this room, not just members of <SpaceName/>.", {}, {
|
||||||
|
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
|
||||||
|
{ _t("You can change this at any time from room settings.") }
|
||||||
|
</p>;
|
||||||
|
} else if (this.state.joinRule === JoinRule.Invite) {
|
||||||
|
publicPrivateLabel = <p>
|
||||||
|
{ _t(
|
||||||
|
"Only people invited will be able to find and join this room.",
|
||||||
|
) }
|
||||||
|
|
||||||
|
{ _t("You can change this at any time from room settings.") }
|
||||||
|
</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2eeSection;
|
let e2eeSection;
|
||||||
if (!this.state.isPublic) {
|
if (this.state.joinRule !== JoinRule.Public) {
|
||||||
let microcopy;
|
let microcopy;
|
||||||
if (privateShouldBeEncrypted()) {
|
if (privateShouldBeEncrypted()) {
|
||||||
if (this.state.canChangeEncryption) {
|
if (this.state.canChangeEncryption) {
|
||||||
|
@ -273,15 +308,31 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
|
let title = _t("Create a room");
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
const name = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
const name = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||||
title = _t("Create a room in %(communityName)s", { communityName: name });
|
title = _t("Create a room in %(communityName)s", { communityName: name });
|
||||||
|
} else if (!this.props.parentSpace) {
|
||||||
|
title = this.state.joinRule === JoinRule.Public ? _t('Create a public room') : _t('Create a private room');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
<div key={JoinRule.Invite} className="mx_CreateRoomDialog_dropdown_invite">
|
||||||
|
{ _t("Private room (invite only)") }
|
||||||
|
</div>,
|
||||||
|
<div key={JoinRule.Public} className="mx_CreateRoomDialog_dropdown_public">
|
||||||
|
{ _t("Public room") }
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.supportsRestricted) {
|
||||||
|
options.unshift(<div key={JoinRule.Restricted} className="mx_CreateRoomDialog_dropdown_restricted">
|
||||||
|
{ _t("Visible to space members") }
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished} title={title}>
|
||||||
title={title}
|
|
||||||
>
|
|
||||||
<form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
|
<form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<Field
|
<Field
|
||||||
|
@ -298,11 +349,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
value={this.state.topic}
|
value={this.state.topic}
|
||||||
className="mx_CreateRoomDialog_topic"
|
className="mx_CreateRoomDialog_topic"
|
||||||
/>
|
/>
|
||||||
<LabelledToggleSwitch
|
|
||||||
label={_t("Make this room public")}
|
<Dropdown
|
||||||
onChange={this.onPublicChange}
|
id="mx_CreateRoomDialog_typeDropdown"
|
||||||
value={this.state.isPublic}
|
className="mx_CreateRoomDialog_typeDropdown"
|
||||||
/>
|
onOptionChange={this.onJoinRuleChange}
|
||||||
|
menuWidth={448}
|
||||||
|
value={this.state.joinRule}
|
||||||
|
label={_t("Room visibility")}
|
||||||
|
>
|
||||||
|
{ options }
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
{ publicPrivateLabel }
|
{ publicPrivateLabel }
|
||||||
{ e2eeSection }
|
{ e2eeSection }
|
||||||
{ aliasField }
|
{ aliasField }
|
||||||
|
|
|
@ -72,7 +72,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
onPrimaryButtonClick={props.onFinished}
|
onPrimaryButtonClick={props.onFinished}
|
||||||
>
|
>
|
||||||
<button onClick={_onLogoutClicked} >
|
<button onClick={_onLogoutClicked}>
|
||||||
{ _t('Sign out') }
|
{ _t('Sign out') }
|
||||||
</button>
|
</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
|
|
|
@ -182,14 +182,23 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
<Field
|
||||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
id="evContent"
|
||||||
|
label={_t("Event Content")}
|
||||||
|
type="text"
|
||||||
|
className="mx_DevTools_textarea"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.state.evContent}
|
||||||
|
onChange={this.onChange}
|
||||||
|
element="textarea" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||||
{ showTglFlip && <div style={{ float: "right" }}>
|
{ showTglFlip && <div style={{ float: "right" }}>
|
||||||
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
<input
|
||||||
|
id="isStateEvent"
|
||||||
|
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={this.state.isStateEvent}
|
checked={this.state.isStateEvent}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
|
@ -282,14 +291,24 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
|
||||||
{ this.textInput('eventType', _t('Event Type')) }
|
{ this.textInput('eventType', _t('Event Type')) }
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
<Field
|
||||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
id="evContent"
|
||||||
|
label={_t("Event Content")}
|
||||||
|
type="text"
|
||||||
|
className="mx_DevTools_textarea"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.state.evContent}
|
||||||
|
onChange={this.onChange}
|
||||||
|
element="textarea"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||||
{ !this.state.message && <div style={{ float: "right" }}>
|
{ !this.state.message && <div style={{ float: "right" }}>
|
||||||
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
<input
|
||||||
|
id="isRoomAccountData"
|
||||||
|
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={this.state.isRoomAccountData}
|
checked={this.state.isRoomAccountData}
|
||||||
disabled={this.props.forceMode}
|
disabled={this.props.forceMode}
|
||||||
|
@ -371,11 +390,18 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div>
|
return <div>
|
||||||
<Field label={_t('Filter results')} autoFocus={true} size={64}
|
<Field
|
||||||
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
label={_t('Filter results')}
|
||||||
|
autoFocus={true}
|
||||||
|
size={64}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.props.query}
|
||||||
|
onChange={this.onQuery}
|
||||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||||
// force re-render so that autoFocus is applied when this component is re-used
|
// force re-render so that autoFocus is applied when this component is re-used
|
||||||
key={this.props.children[0] ? this.props.children[0].key : ''} />
|
key={this.props.children[0] ? this.props.children[0].key : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
<TruncatedList getChildren={this.getChildren}
|
<TruncatedList getChildren={this.getChildren}
|
||||||
getChildCount={this.getChildCount}
|
getChildCount={this.getChildCount}
|
||||||
|
@ -459,11 +485,16 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
||||||
render() {
|
render() {
|
||||||
if (this.state.event) {
|
if (this.state.event) {
|
||||||
if (this.state.editing) {
|
if (this.state.editing) {
|
||||||
return <SendCustomEvent room={this.props.room} forceStateEvent={true} onBack={this.onBack} inputs={{
|
return <SendCustomEvent
|
||||||
|
room={this.props.room}
|
||||||
|
forceStateEvent={true}
|
||||||
|
onBack={this.onBack}
|
||||||
|
inputs={{
|
||||||
eventType: this.state.event.getType(),
|
eventType: this.state.event.getType(),
|
||||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
stateKey: this.state.event.getStateKey(),
|
stateKey: this.state.event.getStateKey(),
|
||||||
}} />;
|
}}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_ViewSource">
|
return <div className="mx_ViewSource">
|
||||||
|
@ -594,7 +625,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
||||||
inputs={{
|
inputs={{
|
||||||
eventType: this.state.event.getType(),
|
eventType: this.state.event.getType(),
|
||||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||||
}} forceMode={true} />;
|
}}
|
||||||
|
forceMode={true}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_ViewSource">
|
return <div className="mx_ViewSource">
|
||||||
|
@ -631,7 +664,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
<div style={{ float: "right" }}>
|
<div style={{ float: "right" }}>
|
||||||
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
<input
|
||||||
|
id="isRoomAccountData"
|
||||||
|
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={this.state.isRoomAccountData}
|
checked={this.state.isRoomAccountData}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
|
@ -1021,8 +1056,13 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
|
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
|
||||||
<Field
|
<Field
|
||||||
label={_t('Filter results')} autoFocus={true} size={64}
|
label={_t('Filter results')}
|
||||||
type="text" autoComplete="off" value={this.state.query} onChange={this.onQueryChange}
|
autoFocus={true}
|
||||||
|
size={64}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.state.query}
|
||||||
|
onChange={this.onQueryChange}
|
||||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||||
/>
|
/>
|
||||||
<table>
|
<table>
|
||||||
|
@ -1040,7 +1080,9 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
||||||
<a href="" onClick={(e) => this.onViewClick(e, i)}>
|
<a href="" onClick={(e) => this.onViewClick(e, i)}>
|
||||||
<code>{ i }</code>
|
<code>{ i }</code>
|
||||||
</a>
|
</a>
|
||||||
<a href="" onClick={(e) => this.onEditClick(e, i)}
|
<a
|
||||||
|
href=""
|
||||||
|
onClick={(e) => this.onEditClick(e, i)}
|
||||||
className='mx_DevTools_SettingsExplorer_edit'
|
className='mx_DevTools_SettingsExplorer_edit'
|
||||||
>
|
>
|
||||||
✏
|
✏
|
||||||
|
@ -1104,18 +1146,26 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Field
|
<Field
|
||||||
id="valExpl" label={_t("Values at explicit levels")} type="text"
|
id="valExpl"
|
||||||
className="mx_DevTools_textarea" element="textarea"
|
label={_t("Values at explicit levels")}
|
||||||
autoComplete="off" value={this.state.explicitValues}
|
type="text"
|
||||||
|
className="mx_DevTools_textarea"
|
||||||
|
element="textarea"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.state.explicitValues}
|
||||||
onChange={this.onExplValuesEdit}
|
onChange={this.onExplValuesEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Field
|
<Field
|
||||||
id="valExpl" label={_t("Values at explicit levels in this room")} type="text"
|
id="valExpl"
|
||||||
className="mx_DevTools_textarea" element="textarea"
|
label={_t("Values at explicit levels in this room")}
|
||||||
autoComplete="off" value={this.state.explicitRoomValues}
|
type="text"
|
||||||
|
className="mx_DevTools_textarea"
|
||||||
|
element="textarea"
|
||||||
|
autoComplete="off"
|
||||||
|
value={this.state.explicitRoomValues}
|
||||||
onChange={this.onExplRoomValuesEdit}
|
onChange={this.onExplRoomValuesEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -144,8 +144,10 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
|
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
|
||||||
<input
|
<input
|
||||||
type="file" style={{ display: "none" }}
|
type="file"
|
||||||
ref={this.avatarUploadRef} accept="image/*"
|
style={{ display: "none" }}
|
||||||
|
ref={this.avatarUploadRef}
|
||||||
|
accept="image/*"
|
||||||
onChange={this.onAvatarChanged}
|
onChange={this.onAvatarChanged}
|
||||||
/>
|
/>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -106,12 +106,12 @@ const Entry: React.FC<IEntryProps> = ({ room, event, matrixClient: cli, onFinish
|
||||||
className = "mx_ForwardList_sending";
|
className = "mx_ForwardList_sending";
|
||||||
disabled = true;
|
disabled = true;
|
||||||
title = _t("Sending");
|
title = _t("Sending");
|
||||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
|
||||||
} else if (sendState === SendState.Sent) {
|
} else if (sendState === SendState.Sent) {
|
||||||
className = "mx_ForwardList_sent";
|
className = "mx_ForwardList_sent";
|
||||||
disabled = true;
|
disabled = true;
|
||||||
title = _t("Sent");
|
title = _t("Sent");
|
||||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
|
||||||
} else {
|
} else {
|
||||||
className = "mx_ForwardList_sendFailed";
|
className = "mx_ForwardList_sendFailed";
|
||||||
disabled = true;
|
disabled = true;
|
||||||
|
@ -204,10 +204,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
||||||
function overflowTile(overflowCount, totalCount) {
|
function overflowTile(overflowCount, totalCount) {
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||||
return (
|
return (
|
||||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
<EntityTile
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={() => setTruncateAt(totalCount)} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={() => setTruncateAt(totalCount)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,18 +133,23 @@ export default class IncomingSasDialog extends React.Component {
|
||||||
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48)
|
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48)
|
||||||
: null;
|
: null;
|
||||||
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
||||||
<BaseAvatar name={oppProfile.displayname}
|
<BaseAvatar
|
||||||
|
name={oppProfile.displayname}
|
||||||
idName={this.props.verifier.userId}
|
idName={this.props.verifier.userId}
|
||||||
url={url}
|
url={url}
|
||||||
width={48} height={48} resizeMethod='crop'
|
width={48}
|
||||||
|
height={48}
|
||||||
|
resizeMethod='crop'
|
||||||
/>
|
/>
|
||||||
<h2>{ oppProfile.displayname }</h2>
|
<h2>{ oppProfile.displayname }</h2>
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.state.opponentProfileError) {
|
} else if (this.state.opponentProfileError) {
|
||||||
profile = <div>
|
profile = <div>
|
||||||
<BaseAvatar name={this.props.verifier.userId.slice(1)}
|
<BaseAvatar
|
||||||
|
name={this.props.verifier.userId.slice(1)}
|
||||||
idName={this.props.verifier.userId}
|
idName={this.props.verifier.userId}
|
||||||
width={48} height={48}
|
width={48}
|
||||||
|
height={48}
|
||||||
/>
|
/>
|
||||||
<h2>{ this.props.verifier.userId }</h2>
|
<h2>{ this.props.verifier.userId }</h2>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
Copyright 2017 New Vector Ltd.
|
|
||||||
Copyright 2019 Bastian Masanek, Noxware IT <matrix@noxware.de>
|
Copyright 2019 Bastian Masanek, Noxware IT <matrix@noxware.de>
|
||||||
|
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,31 +15,31 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ReactNode, KeyboardEvent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
export default class InfoDialog extends React.Component {
|
import { _t } from '../../../languageHandler';
|
||||||
static propTypes = {
|
import * as sdk from '../../../index';
|
||||||
className: PropTypes.string,
|
import { IDialogProps } from "./IDialogProps";
|
||||||
title: PropTypes.string,
|
|
||||||
description: PropTypes.node,
|
|
||||||
button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
|
||||||
onFinished: PropTypes.func,
|
|
||||||
hasCloseButton: PropTypes.bool,
|
|
||||||
onKeyDown: PropTypes.func,
|
|
||||||
fixedWidth: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
title?: string;
|
||||||
|
description?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
button?: boolean | string;
|
||||||
|
hasCloseButton?: boolean;
|
||||||
|
fixedWidth?: boolean;
|
||||||
|
onKeyDown?(event: KeyboardEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class InfoDialog extends React.Component<IProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
hasCloseButton: false,
|
hasCloseButton: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
onFinished = () => {
|
private onFinished = () => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,8 +62,7 @@ export default class InfoDialog extends React.Component {
|
||||||
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
|
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
|
||||||
onPrimaryButtonClick={this.onFinished}
|
onPrimaryButtonClick={this.onFinished}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
>
|
/> }
|
||||||
</DialogButtons> }
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -196,7 +196,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
? <img
|
? <img
|
||||||
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
|
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
|
||||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||||
width={avatarSize} height={avatarSize} />
|
width={avatarSize}
|
||||||
|
height={avatarSize}
|
||||||
|
/>
|
||||||
: <BaseAvatar
|
: <BaseAvatar
|
||||||
className='mx_InviteDialog_userTile_avatar'
|
className='mx_InviteDialog_userTile_avatar'
|
||||||
url={this.props.member.getMxcAvatarUrl()
|
url={this.props.member.getMxcAvatarUrl()
|
||||||
|
@ -214,8 +216,11 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
className='mx_InviteDialog_userTile_remove'
|
className='mx_InviteDialog_userTile_remove'
|
||||||
onClick={this.onRemove}
|
onClick={this.onRemove}
|
||||||
>
|
>
|
||||||
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
<img
|
||||||
alt={_t('Remove')} width={8} height={8}
|
src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||||
|
alt={_t('Remove')}
|
||||||
|
width={8}
|
||||||
|
height={8}
|
||||||
/>
|
/>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
@ -297,7 +302,9 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
const avatar = (this.props.member as ThreepidMember).isEmail
|
const avatar = (this.props.member as ThreepidMember).isEmail
|
||||||
? <img
|
? <img
|
||||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||||
width={avatarSize} height={avatarSize} />
|
width={avatarSize}
|
||||||
|
height={avatarSize}
|
||||||
|
/>
|
||||||
: <BaseAvatar
|
: <BaseAvatar
|
||||||
url={this.props.member.getMxcAvatarUrl()
|
url={this.props.member.getMxcAvatarUrl()
|
||||||
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
||||||
|
@ -1458,7 +1465,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
<p className='mx_InviteDialog_helpText'>
|
<p className='mx_InviteDialog_helpText'>
|
||||||
<img
|
<img
|
||||||
src={require("../../../../res/img/element-icons/info.svg")}
|
src={require("../../../../res/img/element-icons/info.svg")}
|
||||||
width={14} height={14} />
|
width={14}
|
||||||
|
height={14} />
|
||||||
{ " " + _t("Invited people will be able to read old messages.") }
|
{ " " + _t("Invited people will be able to read old messages.") }
|
||||||
</p>;
|
</p>;
|
||||||
}
|
}
|
||||||
|
@ -1534,14 +1542,18 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
// Only show the backspace button if the field has content
|
// Only show the backspace button if the field has content
|
||||||
let dialPadField;
|
let dialPadField;
|
||||||
if (this.state.dialPadValue.length !== 0) {
|
if (this.state.dialPadValue.length !== 0) {
|
||||||
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number"
|
dialPadField = <Field
|
||||||
|
className="mx_InviteDialog_dialPadField"
|
||||||
|
id="dialpad_number"
|
||||||
value={this.state.dialPadValue}
|
value={this.state.dialPadValue}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onChange={this.onDialChange}
|
onChange={this.onDialChange}
|
||||||
postfixComponent={backspaceButton}
|
postfixComponent={backspaceButton}
|
||||||
/>;
|
/>;
|
||||||
} else {
|
} else {
|
||||||
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number"
|
dialPadField = <Field
|
||||||
|
className="mx_InviteDialog_dialPadField"
|
||||||
|
id="dialpad_number"
|
||||||
value={this.state.dialPadValue}
|
value={this.state.dialPadValue}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
onChange={this.onDialChange}
|
onChange={this.onDialChange}
|
||||||
|
@ -1552,14 +1564,19 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
<form onSubmit={this.onDialFormSubmit}>
|
<form onSubmit={this.onDialFormSubmit}>
|
||||||
{ dialPadField }
|
{ dialPadField }
|
||||||
</form>
|
</form>
|
||||||
<Dialpad hasDial={false}
|
<Dialpad
|
||||||
onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress}
|
hasDial={false}
|
||||||
|
onDigitPress={this.onDigitPress}
|
||||||
|
onDeletePress={this.onDeletePress}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
|
tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
|
||||||
dialogContent = <React.Fragment>
|
dialogContent = <React.Fragment>
|
||||||
<TabbedView tabs={tabs} initialTabId={this.state.currentTabId}
|
<TabbedView
|
||||||
tabLocation={TabLocation.TOP} onChange={this.onTabChange}
|
tabs={tabs}
|
||||||
|
initialTabId={this.state.currentTabId}
|
||||||
|
tabLocation={TabLocation.TOP}
|
||||||
|
onChange={this.onTabChange}
|
||||||
/>
|
/>
|
||||||
{ consultConnectSection }
|
{ consultConnectSection }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
192
src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
Normal file
192
src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import SearchBox from "../../structures/SearchBox";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
room: Room;
|
||||||
|
selected?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Entry = ({ room, checked, onChange }) => {
|
||||||
|
const localRoom = room instanceof Room;
|
||||||
|
|
||||||
|
let description;
|
||||||
|
if (localRoom) {
|
||||||
|
description = _t("%(count)s members", { count: room.getJoinedMemberCount() });
|
||||||
|
const numChildRooms = SpaceStore.instance.getChildRooms(room.roomId).length;
|
||||||
|
if (numChildRooms > 0) {
|
||||||
|
description += " · " + _t("%(count)s rooms", { count: numChildRooms });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <label className="mx_ManageRestrictedJoinRuleDialog_entry">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{ localRoom
|
||||||
|
? <RoomAvatar room={room} height={20} width={20} />
|
||||||
|
: <RoomAvatar oobData={room} height={20} width={20} />
|
||||||
|
}
|
||||||
|
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{ room.name }</span>
|
||||||
|
</div>
|
||||||
|
{ description && <div className="mx_ManageRestrictedJoinRuleDialog_entry_description">
|
||||||
|
{ description }
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
<StyledCheckbox
|
||||||
|
onChange={onChange ? (e) => onChange(e.target.checked) : null}
|
||||||
|
checked={checked}
|
||||||
|
disabled={!onChange}
|
||||||
|
/>
|
||||||
|
</label>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [], onFinished }) => {
|
||||||
|
const cli = room.client;
|
||||||
|
const [newSelected, setNewSelected] = useState(new Set<string>(selected));
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
const [spacesContainingRoom, otherEntries] = useMemo(() => {
|
||||||
|
const spaces = cli.getVisibleRooms().filter(r => r.getMyMembership() === "join" && r.isSpaceRoom());
|
||||||
|
return [
|
||||||
|
spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r).has(room.roomId)),
|
||||||
|
selected.map(roomId => {
|
||||||
|
const room = cli.getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return { roomId, name: roomId } as Room;
|
||||||
|
}
|
||||||
|
if (room.getMyMembership() !== "join" || !room.isSpaceRoom()) {
|
||||||
|
return room;
|
||||||
|
}
|
||||||
|
}).filter(Boolean),
|
||||||
|
];
|
||||||
|
}, [cli, selected, room.roomId]);
|
||||||
|
|
||||||
|
const [filteredSpacesContainingRooms, filteredOtherEntries] = useMemo(() => [
|
||||||
|
spacesContainingRoom.filter(r => r.name.toLowerCase().includes(lcQuery)),
|
||||||
|
otherEntries.filter(r => r.name.toLowerCase().includes(lcQuery)),
|
||||||
|
], [spacesContainingRoom, otherEntries, lcQuery]);
|
||||||
|
|
||||||
|
const onChange = (checked: boolean, room: Room): void => {
|
||||||
|
if (checked) {
|
||||||
|
newSelected.add(room.roomId);
|
||||||
|
} else {
|
||||||
|
newSelected.delete(room.roomId);
|
||||||
|
}
|
||||||
|
setNewSelected(new Set(newSelected));
|
||||||
|
};
|
||||||
|
|
||||||
|
let inviteOnlyWarning;
|
||||||
|
if (newSelected.size < 1) {
|
||||||
|
inviteOnlyWarning = <div className="mx_ManageRestrictedJoinRuleDialog_section_info">
|
||||||
|
{ _t("You're removing all spaces. Access will default to invite only") }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={_t("Select spaces")}
|
||||||
|
className="mx_ManageRestrictedJoinRuleDialog"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
{ _t("Decide which spaces can access this room. " +
|
||||||
|
"If a space is selected, its members can find and join <RoomName/>.", {}, {
|
||||||
|
RoomName: () => <b>{ room.name }</b>,
|
||||||
|
}) }
|
||||||
|
</p>
|
||||||
|
<MatrixClientContext.Provider value={cli}>
|
||||||
|
<SearchBox
|
||||||
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
placeholder={_t("Search spaces")}
|
||||||
|
onSearch={setQuery}
|
||||||
|
autoComplete={true}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content">
|
||||||
|
{ filteredSpacesContainingRooms.length > 0 ? (
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_section">
|
||||||
|
<h3>{ _t("Spaces you know that contain this room") }</h3>
|
||||||
|
{ filteredSpacesContainingRooms.map(space => {
|
||||||
|
return <Entry
|
||||||
|
key={space.roomId}
|
||||||
|
room={space}
|
||||||
|
checked={newSelected.has(space.roomId)}
|
||||||
|
onChange={(checked: boolean) => {
|
||||||
|
onChange(checked, space);
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
) : undefined }
|
||||||
|
|
||||||
|
{ filteredOtherEntries.length > 0 ? (
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_section">
|
||||||
|
<h3>{ _t("Other spaces or rooms you might not know") }</h3>
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_section_info">
|
||||||
|
<div>{ _t("These are likely ones other room admins are a part of.") }</div>
|
||||||
|
</div>
|
||||||
|
{ filteredOtherEntries.map(space => {
|
||||||
|
return <Entry
|
||||||
|
key={space.roomId}
|
||||||
|
room={space}
|
||||||
|
checked={newSelected.has(space.roomId)}
|
||||||
|
onChange={(checked: boolean) => {
|
||||||
|
onChange(checked, space);
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
{ filteredSpacesContainingRooms.length + filteredOtherEntries.length < 1
|
||||||
|
? <span className="mx_ManageRestrictedJoinRuleDialog_noResults">
|
||||||
|
{ _t("No results") }
|
||||||
|
</span>
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_footer">
|
||||||
|
{ inviteOnlyWarning }
|
||||||
|
<div className="mx_ManageRestrictedJoinRuleDialog_footer_buttons">
|
||||||
|
<AccessibleButton kind="primary_outline" onClick={() => onFinished()}>
|
||||||
|
{ _t("Cancel") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton kind="primary" onClick={() => onFinished(Array.from(newSelected))}>
|
||||||
|
{ _t("Confirm") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManageRestrictedJoinRuleDialog;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,19 +15,29 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { upgradeRoom } from "../../../utils/RoomUpgrade";
|
||||||
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import ErrorDialog from './ErrorDialog';
|
||||||
|
import DialogButtons from '../elements/DialogButtons';
|
||||||
|
import Spinner from "../elements/Spinner";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
room: Room;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
busy: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.RoomUpgradeDialog")
|
@replaceableComponent("views.dialogs.RoomUpgradeDialog")
|
||||||
export default class RoomUpgradeDialog extends React.Component {
|
export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
private targetVersion: string;
|
||||||
room: PropTypes.object.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
busy: true,
|
busy: true,
|
||||||
|
@ -35,20 +45,19 @@ export default class RoomUpgradeDialog extends React.Component {
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const recommended = await this.props.room.getRecommendedVersion();
|
const recommended = await this.props.room.getRecommendedVersion();
|
||||||
this._targetVersion = recommended.version;
|
this.targetVersion = recommended.version;
|
||||||
this.setState({ busy: false });
|
this.setState({ busy: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick = () => {
|
private onCancelClick = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onUpgradeClick = () => {
|
private onUpgradeClick = (): void => {
|
||||||
this.setState({ busy: true });
|
this.setState({ busy: true });
|
||||||
MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => {
|
upgradeRoom(this.props.room, this.targetVersion, false, false).then(() => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to upgrade room', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to upgrade room', '', ErrorDialog, {
|
||||||
title: _t("Failed to upgrade room"),
|
title: _t("Failed to upgrade room"),
|
||||||
description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
|
description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
|
||||||
|
@ -59,29 +68,22 @@ export default class RoomUpgradeDialog extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
|
||||||
|
|
||||||
let buttons;
|
let buttons;
|
||||||
if (this.state.busy) {
|
if (this.state.busy) {
|
||||||
buttons = <Spinner />;
|
buttons = <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
buttons = <DialogButtons
|
buttons = <DialogButtons
|
||||||
primaryButton={_t(
|
primaryButton={_t('Upgrade this room to version %(version)s', { version: this.targetVersion })}
|
||||||
'Upgrade this room to version %(version)s',
|
|
||||||
{ version: this._targetVersion },
|
|
||||||
)}
|
|
||||||
primaryButtonClass="danger"
|
primaryButtonClass="danger"
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onPrimaryButtonClick={this._onUpgradeClick}
|
onPrimaryButtonClick={this.onUpgradeClick}
|
||||||
focus={this.props.focus}
|
onCancel={this.onCancelClick}
|
||||||
onCancel={this._onCancelClick}
|
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_RoomUpgradeDialog"
|
<BaseDialog
|
||||||
|
className="mx_RoomUpgradeDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Upgrade Room Version")}
|
title={_t("Upgrade Room Version")}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
|
@ -97,8 +99,10 @@ export default class RoomUpgradeDialog extends React.Component {
|
||||||
<ol>
|
<ol>
|
||||||
<li>{ _t("Create a new room with the same name, description and avatar") }</li>
|
<li>{ _t("Create a new room with the same name, description and avatar") }</li>
|
||||||
<li>{ _t("Update any local room aliases to point to the new room") }</li>
|
<li>{ _t("Update any local room aliases to point to the new room") }</li>
|
||||||
<li>{ _t("Stop users from speaking in the old version of the room, and post a message advising users to move to the new room") }</li>
|
<li>{ _t("Stop users from speaking in the old version of the room, " +
|
||||||
<li>{ _t("Put a link back to the old room at the start of the new room so people can see old messages") }</li>
|
"and post a message advising users to move to the new room") }</li>
|
||||||
|
<li>{ _t("Put a link back to the old room at the start of the new room " +
|
||||||
|
"so people can see old messages") }</li>
|
||||||
</ol>
|
</ol>
|
||||||
{ buttons }
|
{ buttons }
|
||||||
</BaseDialog>
|
</BaseDialog>
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,73 +14,82 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
||||||
|
import { JoinRule } from 'matrix-js-sdk/src/@types/partials';
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import * as sdk from "../../../index";
|
|
||||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import BugReportDialog from './BugReportDialog';
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
roomId: string;
|
||||||
|
targetVersion: string;
|
||||||
|
description?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
inviteUsersToNewRoom: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.RoomUpgradeWarningDialog")
|
@replaceableComponent("views.dialogs.RoomUpgradeWarningDialog")
|
||||||
export default class RoomUpgradeWarningDialog extends React.Component {
|
export default class RoomUpgradeWarningDialog extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
private readonly isPrivate: boolean;
|
||||||
onFinished: PropTypes.func.isRequired,
|
private readonly currentVersion: string;
|
||||||
roomId: PropTypes.string.isRequired,
|
|
||||||
targetVersion: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||||
const joinRules = room ? room.currentState.getStateEvents("m.room.join_rules", "") : null;
|
const joinRules = room?.currentState.getStateEvents(EventType.RoomJoinRules, "");
|
||||||
const isPrivate = joinRules ? joinRules.getContent()['join_rule'] !== 'public' : true;
|
this.isPrivate = joinRules?.getContent()['join_rule'] !== JoinRule.Public ?? true;
|
||||||
|
this.currentVersion = room?.getVersion() || "1";
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentVersion: room ? room.getVersion() : "1",
|
|
||||||
isPrivate,
|
|
||||||
inviteUsersToNewRoom: true,
|
inviteUsersToNewRoom: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onContinue = () => {
|
private onContinue = () => {
|
||||||
this.props.onFinished({ continue: true, invite: this.state.isPrivate && this.state.inviteUsersToNewRoom });
|
this.props.onFinished({ continue: true, invite: this.isPrivate && this.state.inviteUsersToNewRoom });
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCancel = () => {
|
private onCancel = () => {
|
||||||
this.props.onFinished({ continue: false, invite: false });
|
this.props.onFinished({ continue: false, invite: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
_onInviteUsersToggle = (newVal) => {
|
private onInviteUsersToggle = (inviteUsersToNewRoom: boolean) => {
|
||||||
this.setState({ inviteUsersToNewRoom: newVal });
|
this.setState({ inviteUsersToNewRoom });
|
||||||
};
|
};
|
||||||
|
|
||||||
_openBugReportDialog = (e) => {
|
private openBugReportDialog = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
|
||||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
|
|
||||||
let inviteToggle = null;
|
let inviteToggle = null;
|
||||||
if (this.state.isPrivate) {
|
if (this.isPrivate) {
|
||||||
inviteToggle = (
|
inviteToggle = (
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
value={this.state.inviteUsersToNewRoom}
|
value={this.state.inviteUsersToNewRoom}
|
||||||
onChange={this._onInviteUsersToggle}
|
onChange={this.onInviteUsersToggle}
|
||||||
label={_t("Automatically invite users")} />
|
label={_t("Automatically invite members from this room to the new one")} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = this.state.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
const title = this.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
||||||
|
|
||||||
let bugReports = (
|
let bugReports = (
|
||||||
<p>
|
<p>
|
||||||
|
@ -101,7 +110,7 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a": (sub) => {
|
"a": (sub) => {
|
||||||
return <a href='#' onClick={this._openBugReportDialog}>{ sub }</a>;
|
return <a href='#' onClick={this.openBugReportDialog}>{ sub }</a>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
) }
|
) }
|
||||||
|
@ -119,18 +128,26 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ this.props.description || _t(
|
||||||
"Upgrading a room is an advanced action and is usually recommended when a room " +
|
"Upgrading a room is an advanced action and is usually recommended when a room " +
|
||||||
"is unstable due to bugs, missing features or security vulnerabilities.",
|
"is unstable due to bugs, missing features or security vulnerabilities.",
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
{ _t(
|
||||||
|
"<b>Please note upgrading will make a new version of the room</b>. " +
|
||||||
|
"All current messages will stay in this archived room.", {}, {
|
||||||
|
b: sub => <b>{ sub }</b>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
</p>
|
||||||
{ bugReports }
|
{ bugReports }
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
|
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
oldVersion: () => <code>{ this.state.currentVersion }</code>,
|
oldVersion: () => <code>{ this.currentVersion }</code>,
|
||||||
newVersion: () => <code>{ this.props.targetVersion }</code>,
|
newVersion: () => <code>{ this.props.targetVersion }</code>,
|
||||||
},
|
},
|
||||||
) }
|
) }
|
||||||
|
@ -139,9 +156,9 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Upgrade")}
|
primaryButton={_t("Upgrade")}
|
||||||
onPrimaryButtonClick={this._onContinue}
|
onPrimaryButtonClick={this.onContinue}
|
||||||
cancelButton={_t("Cancel")}
|
cancelButton={_t("Cancel")}
|
||||||
onCancel={this._onCancel}
|
onCancel={this.onCancel}
|
||||||
/>
|
/>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
|
@ -85,7 +85,9 @@ export default class SessionRestoreErrorDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_ErrorDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={_t('Unable to restore session')}
|
title={_t('Unable to restore session')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
|
|
|
@ -54,7 +54,9 @@ export default class StorageEvictedDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
<BaseDialog
|
||||||
|
className="mx_ErrorDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
title={_t('Missing session data')}
|
title={_t('Missing session data')}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
|
|
|
@ -287,7 +287,8 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
||||||
<div className="mx_AccessSecretStorageDialog_reset">
|
<div className="mx_AccessSecretStorageDialog_reset">
|
||||||
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
|
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
|
||||||
a: (sub) => <a
|
a: (sub) => <a
|
||||||
href="" onClick={this.onResetAllClick}
|
href=""
|
||||||
|
onClick={this.onResetAllClick}
|
||||||
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
|
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -399,7 +399,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
let keyStatus;
|
let keyStatus;
|
||||||
if (this.state.recoveryKey.length === 0) {
|
if (this.state.recoveryKey.length === 0) {
|
||||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>;
|
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus" />;
|
||||||
} else if (this.state.recoveryKeyValid) {
|
} else if (this.state.recoveryKeyValid) {
|
||||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
|
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
|
||||||
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }
|
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }
|
||||||
|
|
|
@ -122,7 +122,7 @@ export default class AddressTile extends React.Component<IProps> {
|
||||||
let dismiss;
|
let dismiss;
|
||||||
if (this.props.canDismiss) {
|
if (this.props.canDismiss) {
|
||||||
dismiss = (
|
dismiss = (
|
||||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
|
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed}>
|
||||||
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,7 +51,8 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_desktopCapturerSourcePicker_stream_button"
|
className="mx_desktopCapturerSourcePicker_stream_button"
|
||||||
title={this.props.source.name}
|
title={this.props.source.name}
|
||||||
onClick={this.onClick} >
|
onClick={this.onClick}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
|
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
|
||||||
src={this.props.source.thumbnailURL}
|
src={this.props.source.thumbnailURL}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,34 +15,38 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { ChangeEvent, createRef, CSSProperties, ReactElement, ReactNode, Ref } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { Key } from "../../../Keyboard";
|
import { Key } from "../../../Keyboard";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
class MenuOption extends React.Component {
|
interface IMenuOptionProps {
|
||||||
constructor(props) {
|
children: ReactElement;
|
||||||
super(props);
|
highlighted?: boolean;
|
||||||
this._onMouseEnter = this._onMouseEnter.bind(this);
|
dropdownKey: string;
|
||||||
this._onClick = this._onClick.bind(this);
|
id?: string;
|
||||||
}
|
inputRef?: Ref<HTMLDivElement>;
|
||||||
|
onClick(dropdownKey: string): void;
|
||||||
|
onMouseEnter(dropdownKey: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MenuOption extends React.Component<IMenuOptionProps> {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
_onMouseEnter() {
|
private onMouseEnter = () => {
|
||||||
this.props.onMouseEnter(this.props.dropdownKey);
|
this.props.onMouseEnter(this.props.dropdownKey);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onClick(e) {
|
private onClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.props.onClick(this.props.dropdownKey);
|
this.props.onClick(this.props.dropdownKey);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const optClasses = classnames({
|
const optClasses = classnames({
|
||||||
|
@ -54,8 +57,8 @@ class MenuOption extends React.Component {
|
||||||
return <div
|
return <div
|
||||||
id={this.props.id}
|
id={this.props.id}
|
||||||
className={optClasses}
|
className={optClasses}
|
||||||
onClick={this._onClick}
|
onClick={this.onClick}
|
||||||
onMouseEnter={this._onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={this.props.highlighted}
|
aria-selected={this.props.highlighted}
|
||||||
ref={this.props.inputRef}
|
ref={this.props.inputRef}
|
||||||
|
@ -65,91 +68,97 @@ class MenuOption extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuOption.propTypes = {
|
interface IProps {
|
||||||
children: PropTypes.oneOfType([
|
id: string;
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
// ARIA label
|
||||||
PropTypes.node,
|
label: string;
|
||||||
]),
|
value?: string;
|
||||||
highlighted: PropTypes.bool,
|
className?: string;
|
||||||
dropdownKey: PropTypes.string,
|
children: ReactElement[];
|
||||||
onClick: PropTypes.func.isRequired,
|
// negative for consistency with HTML
|
||||||
onMouseEnter: PropTypes.func.isRequired,
|
disabled?: boolean;
|
||||||
inputRef: PropTypes.any,
|
// The width that the dropdown should be. If specified,
|
||||||
};
|
// the dropped-down part of the menu will be set to this
|
||||||
|
// width.
|
||||||
|
menuWidth?: number;
|
||||||
|
searchEnabled?: boolean;
|
||||||
|
// Called when the selected option changes
|
||||||
|
onOptionChange(dropdownKey: string): void;
|
||||||
|
// Called when the value of the search field changes
|
||||||
|
onSearchChange?(query: string): void;
|
||||||
|
// Function that, given the key of an option, returns
|
||||||
|
// a node representing that option to be displayed in the
|
||||||
|
// box itself as the currently-selected option (ie. as
|
||||||
|
// opposed to in the actual dropped-down part). If
|
||||||
|
// unspecified, the appropriate child element is used as
|
||||||
|
// in the dropped-down menu.
|
||||||
|
getShortOption?(value: string): ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
expanded: boolean;
|
||||||
|
highlightedOption: string | null;
|
||||||
|
searchQuery: string;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reusable dropdown select control, akin to react-select,
|
* Reusable dropdown select control, akin to react-select,
|
||||||
* but somewhat simpler as react-select is 79KB of minified
|
* but somewhat simpler as react-select is 79KB of minified
|
||||||
* javascript.
|
* javascript.
|
||||||
*
|
|
||||||
* TODO: Port NetworkDropdown to use this.
|
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.elements.Dropdown")
|
@replaceableComponent("views.elements.Dropdown")
|
||||||
export default class Dropdown extends React.Component {
|
export default class Dropdown extends React.Component<IProps, IState> {
|
||||||
constructor(props) {
|
private readonly buttonRef = createRef<HTMLDivElement>();
|
||||||
|
private dropdownRootElement: HTMLDivElement = null;
|
||||||
|
private ignoreEvent: MouseEvent = null;
|
||||||
|
private childrenByKey: Record<string, ReactNode> = {};
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.dropdownRootElement = null;
|
this.reindexChildren(this.props.children);
|
||||||
this.ignoreEvent = null;
|
|
||||||
|
|
||||||
this._onInputClick = this._onInputClick.bind(this);
|
const firstChild = React.Children.toArray(props.children)[0] as ReactElement;
|
||||||
this._onRootClick = this._onRootClick.bind(this);
|
|
||||||
this._onDocumentClick = this._onDocumentClick.bind(this);
|
|
||||||
this._onMenuOptionClick = this._onMenuOptionClick.bind(this);
|
|
||||||
this._onInputChange = this._onInputChange.bind(this);
|
|
||||||
this._collectRoot = this._collectRoot.bind(this);
|
|
||||||
this._collectInputTextBox = this._collectInputTextBox.bind(this);
|
|
||||||
this._setHighlightedOption = this._setHighlightedOption.bind(this);
|
|
||||||
|
|
||||||
this.inputTextBox = null;
|
|
||||||
|
|
||||||
this._reindexChildren(this.props.children);
|
|
||||||
|
|
||||||
const firstChild = React.Children.toArray(props.children)[0];
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// True if the menu is dropped-down
|
// True if the menu is dropped-down
|
||||||
expanded: false,
|
expanded: false,
|
||||||
// The key of the highlighted option
|
// The key of the highlighted option
|
||||||
// (the option that would become selected if you pressed enter)
|
// (the option that would become selected if you pressed enter)
|
||||||
highlightedOption: firstChild ? firstChild.key : null,
|
highlightedOption: firstChild ? firstChild.key as string : null,
|
||||||
// the current search query
|
// the current search query
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
|
||||||
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
|
||||||
this._button = createRef();
|
|
||||||
// Listen for all clicks on the document so we can close the
|
// Listen for all clicks on the document so we can close the
|
||||||
// menu when the user clicks somewhere else
|
// menu when the user clicks somewhere else
|
||||||
document.addEventListener('click', this._onDocumentClick, false);
|
document.addEventListener('click', this.onDocumentClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('click', this._onDocumentClick, false);
|
document.removeEventListener('click', this.onDocumentClick, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
|
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line
|
||||||
if (!nextProps.children || nextProps.children.length === 0) {
|
if (!nextProps.children || nextProps.children.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._reindexChildren(nextProps.children);
|
this.reindexChildren(nextProps.children);
|
||||||
const firstChild = nextProps.children[0];
|
const firstChild = nextProps.children[0];
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: firstChild ? firstChild.key : null,
|
highlightedOption: firstChild ? firstChild.key : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_reindexChildren(children) {
|
private reindexChildren(children: ReactElement[]): void {
|
||||||
this.childrenByKey = {};
|
this.childrenByKey = {};
|
||||||
React.Children.forEach(children, (child) => {
|
React.Children.forEach(children, (child) => {
|
||||||
this.childrenByKey[child.key] = child;
|
this.childrenByKey[child.key] = child;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDocumentClick(ev) {
|
private onDocumentClick = (ev: MouseEvent) => {
|
||||||
// Close the dropdown if the user clicks anywhere that isn't
|
// Close the dropdown if the user clicks anywhere that isn't
|
||||||
// within our root element
|
// within our root element
|
||||||
if (ev !== this.ignoreEvent) {
|
if (ev !== this.ignoreEvent) {
|
||||||
|
@ -157,9 +166,9 @@ export default class Dropdown extends React.Component {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onRootClick(ev) {
|
private onRootClick = (ev: MouseEvent) => {
|
||||||
// This captures any clicks that happen within our elements,
|
// This captures any clicks that happen within our elements,
|
||||||
// such that we can then ignore them when they're seen by the
|
// such that we can then ignore them when they're seen by the
|
||||||
// click listener on the document handler, ie. not close the
|
// click listener on the document handler, ie. not close the
|
||||||
|
@ -167,9 +176,9 @@ export default class Dropdown extends React.Component {
|
||||||
// NB. We can't just stopPropagation() because then the event
|
// NB. We can't just stopPropagation() because then the event
|
||||||
// doesn't reach the React onClick().
|
// doesn't reach the React onClick().
|
||||||
this.ignoreEvent = ev;
|
this.ignoreEvent = ev;
|
||||||
}
|
};
|
||||||
|
|
||||||
_onInputClick(ev) {
|
private onInputClick = (ev: React.MouseEvent) => {
|
||||||
if (this.props.disabled) return;
|
if (this.props.disabled) return;
|
||||||
|
|
||||||
if (!this.state.expanded) {
|
if (!this.state.expanded) {
|
||||||
|
@ -178,24 +187,24 @@ export default class Dropdown extends React.Component {
|
||||||
});
|
});
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_close() {
|
private close() {
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: false,
|
expanded: false,
|
||||||
});
|
});
|
||||||
// their focus was on the input, its getting unmounted, move it to the button
|
// their focus was on the input, its getting unmounted, move it to the button
|
||||||
if (this._button.current) {
|
if (this.buttonRef.current) {
|
||||||
this._button.current.focus();
|
this.buttonRef.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onMenuOptionClick(dropdownKey) {
|
private onMenuOptionClick = (dropdownKey: string) => {
|
||||||
this._close();
|
this.close();
|
||||||
this.props.onOptionChange(dropdownKey);
|
this.props.onOptionChange(dropdownKey);
|
||||||
}
|
};
|
||||||
|
|
||||||
_onInputKeyDown = (e) => {
|
private onInputKeyDown = (e: React.KeyboardEvent) => {
|
||||||
let handled = true;
|
let handled = true;
|
||||||
|
|
||||||
// These keys don't generate keypress events and so needs to be on keyup
|
// These keys don't generate keypress events and so needs to be on keyup
|
||||||
|
@ -204,16 +213,16 @@ export default class Dropdown extends React.Component {
|
||||||
this.props.onOptionChange(this.state.highlightedOption);
|
this.props.onOptionChange(this.state.highlightedOption);
|
||||||
// fallthrough
|
// fallthrough
|
||||||
case Key.ESCAPE:
|
case Key.ESCAPE:
|
||||||
this._close();
|
this.close();
|
||||||
break;
|
break;
|
||||||
case Key.ARROW_DOWN:
|
case Key.ARROW_DOWN:
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: this._nextOption(this.state.highlightedOption),
|
highlightedOption: this.nextOption(this.state.highlightedOption),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Key.ARROW_UP:
|
case Key.ARROW_UP:
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: this._prevOption(this.state.highlightedOption),
|
highlightedOption: this.prevOption(this.state.highlightedOption),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -224,53 +233,46 @@ export default class Dropdown extends React.Component {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
_onInputChange(e) {
|
private onInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
searchQuery: e.target.value,
|
searchQuery: e.currentTarget.value,
|
||||||
});
|
});
|
||||||
if (this.props.onSearchChange) {
|
if (this.props.onSearchChange) {
|
||||||
this.props.onSearchChange(e.target.value);
|
this.props.onSearchChange(e.currentTarget.value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
_collectRoot(e) {
|
private collectRoot = (e: HTMLDivElement) => {
|
||||||
if (this.dropdownRootElement) {
|
if (this.dropdownRootElement) {
|
||||||
this.dropdownRootElement.removeEventListener(
|
this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
|
||||||
'click', this._onRootClick, false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (e) {
|
if (e) {
|
||||||
e.addEventListener('click', this._onRootClick, false);
|
e.addEventListener('click', this.onRootClick, false);
|
||||||
}
|
}
|
||||||
this.dropdownRootElement = e;
|
this.dropdownRootElement = e;
|
||||||
}
|
};
|
||||||
|
|
||||||
_collectInputTextBox(e) {
|
private setHighlightedOption = (optionKey: string) => {
|
||||||
this.inputTextBox = e;
|
|
||||||
if (e) e.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
_setHighlightedOption(optionKey) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: optionKey,
|
highlightedOption: optionKey,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
_nextOption(optionKey) {
|
private nextOption(optionKey: string): string {
|
||||||
const keys = Object.keys(this.childrenByKey);
|
const keys = Object.keys(this.childrenByKey);
|
||||||
const index = keys.indexOf(optionKey);
|
const index = keys.indexOf(optionKey);
|
||||||
return keys[(index + 1) % keys.length];
|
return keys[(index + 1) % keys.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
_prevOption(optionKey) {
|
private prevOption(optionKey: string): string {
|
||||||
const keys = Object.keys(this.childrenByKey);
|
const keys = Object.keys(this.childrenByKey);
|
||||||
const index = keys.indexOf(optionKey);
|
const index = keys.indexOf(optionKey);
|
||||||
return keys[(index - 1) % keys.length];
|
return keys[(index - 1) % keys.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
_scrollIntoView(node) {
|
private scrollIntoView(node: Element) {
|
||||||
if (node) {
|
if (node) {
|
||||||
node.scrollIntoView({
|
node.scrollIntoView({
|
||||||
block: "nearest",
|
block: "nearest",
|
||||||
|
@ -279,18 +281,18 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMenuOptions() {
|
private getMenuOptions() {
|
||||||
const options = React.Children.map(this.props.children, (child) => {
|
const options = React.Children.map(this.props.children, (child) => {
|
||||||
const highlighted = this.state.highlightedOption === child.key;
|
const highlighted = this.state.highlightedOption === child.key;
|
||||||
return (
|
return (
|
||||||
<MenuOption
|
<MenuOption
|
||||||
id={`${this.props.id}__${child.key}`}
|
id={`${this.props.id}__${child.key}`}
|
||||||
key={child.key}
|
key={child.key}
|
||||||
dropdownKey={child.key}
|
dropdownKey={child.key as string}
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
onMouseEnter={this._setHighlightedOption}
|
onMouseEnter={this.setHighlightedOption}
|
||||||
onClick={this._onMenuOptionClick}
|
onClick={this.onMenuOptionClick}
|
||||||
inputRef={highlighted ? this._scrollIntoView : undefined}
|
inputRef={highlighted ? this.scrollIntoView : undefined}
|
||||||
>
|
>
|
||||||
{ child }
|
{ child }
|
||||||
</MenuOption>
|
</MenuOption>
|
||||||
|
@ -307,7 +309,7 @@ export default class Dropdown extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
let currentValue;
|
let currentValue;
|
||||||
|
|
||||||
const menuStyle = {};
|
const menuStyle: CSSProperties = {};
|
||||||
if (this.props.menuWidth) menuStyle.width = this.props.menuWidth;
|
if (this.props.menuWidth) menuStyle.width = this.props.menuWidth;
|
||||||
|
|
||||||
let menu;
|
let menu;
|
||||||
|
@ -316,10 +318,10 @@ export default class Dropdown extends React.Component {
|
||||||
currentValue = (
|
currentValue = (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
autoFocus={true}
|
||||||
className="mx_Dropdown_option"
|
className="mx_Dropdown_option"
|
||||||
ref={this._collectInputTextBox}
|
onKeyDown={this.onInputKeyDown}
|
||||||
onKeyDown={this._onInputKeyDown}
|
onChange={this.onInputChange}
|
||||||
onChange={this._onInputChange}
|
|
||||||
value={this.state.searchQuery}
|
value={this.state.searchQuery}
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-autocomplete="list"
|
aria-autocomplete="list"
|
||||||
|
@ -332,7 +334,7 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
menu = (
|
menu = (
|
||||||
<div className="mx_Dropdown_menu" style={menuStyle} role="listbox" id={`${this.props.id}_listbox`}>
|
<div className="mx_Dropdown_menu" style={menuStyle} role="listbox" id={`${this.props.id}_listbox`}>
|
||||||
{ this._getMenuOptions() }
|
{ this.getMenuOptions() }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -356,14 +358,14 @@ export default class Dropdown extends React.Component {
|
||||||
|
|
||||||
// Note the menu sits inside the AccessibleButton div so it's anchored
|
// Note the menu sits inside the AccessibleButton div so it's anchored
|
||||||
// to the input, but overflows below it. The root contains both.
|
// to the input, but overflows below it. The root contains both.
|
||||||
return <div className={classnames(dropdownClasses)} ref={this._collectRoot}>
|
return <div className={classnames(dropdownClasses)} ref={this.collectRoot}>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_Dropdown_input mx_no_textinput"
|
className="mx_Dropdown_input mx_no_textinput"
|
||||||
onClick={this._onInputClick}
|
onClick={this.onInputClick}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-expanded={this.state.expanded}
|
aria-expanded={this.state.expanded}
|
||||||
disabled={this.props.disabled}
|
disabled={this.props.disabled}
|
||||||
inputRef={this._button}
|
inputRef={this.buttonRef}
|
||||||
aria-label={this.props.label}
|
aria-label={this.props.label}
|
||||||
aria-describedby={`${this.props.id}_value`}
|
aria-describedby={`${this.props.id}_value`}
|
||||||
>
|
>
|
||||||
|
@ -374,28 +376,3 @@ export default class Dropdown extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dropdown.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
// The width that the dropdown should be. If specified,
|
|
||||||
// the dropped-down part of the menu will be set to this
|
|
||||||
// width.
|
|
||||||
menuWidth: PropTypes.number,
|
|
||||||
// Called when the selected option changes
|
|
||||||
onOptionChange: PropTypes.func.isRequired,
|
|
||||||
// Called when the value of the search field changes
|
|
||||||
onSearchChange: PropTypes.func,
|
|
||||||
searchEnabled: PropTypes.bool,
|
|
||||||
// Function that, given the key of an option, returns
|
|
||||||
// a node representing that option to be displayed in the
|
|
||||||
// box itself as the currently-selected option (ie. as
|
|
||||||
// opposed to in the actual dropped-down part). If
|
|
||||||
// unspecified, the appropriate child element is used as
|
|
||||||
// in the dropped-down menu.
|
|
||||||
getShortOption: PropTypes.func,
|
|
||||||
value: PropTypes.string,
|
|
||||||
// negative for consistency with HTML
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
// ARIA label
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
};
|
|
|
@ -419,7 +419,8 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
const avatar = (
|
const avatar = (
|
||||||
<MemberAvatar
|
<MemberAvatar
|
||||||
member={mxEvent.sender}
|
member={mxEvent.sender}
|
||||||
width={32} height={32}
|
width={32}
|
||||||
|
height={32}
|
||||||
viewUserOnClick={true}
|
viewUserOnClick={true}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -438,7 +439,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
// an empty div here, since the panel uses space-between
|
// an empty div here, since the panel uses space-between
|
||||||
// and we want the same placement of elements
|
// and we want the same placement of elements
|
||||||
info = (
|
info = (
|
||||||
<div></div>
|
<div />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,15 +463,15 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
||||||
title={_t("Zoom out")}
|
title={_t("Zoom out")}
|
||||||
onClick={this.onZoomOutClick}>
|
onClick={this.onZoomOutClick}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
);
|
);
|
||||||
zoomInButton = (
|
zoomInButton = (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_zoomIn"
|
className="mx_ImageView_button mx_ImageView_button_zoomIn"
|
||||||
title={_t("Zoom in")}
|
title={_t("Zoom in")}
|
||||||
onClick={this.onZoomInClick}>
|
onClick={this.onZoomInClick}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,24 +493,24 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
||||||
title={_t("Rotate Left")}
|
title={_t("Rotate Left")}
|
||||||
onClick={this.onRotateCounterClockwiseClick}>
|
onClick={this.onRotateCounterClockwiseClick}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
||||||
title={_t("Rotate Right")}
|
title={_t("Rotate Right")}
|
||||||
onClick={this.onRotateClockwiseClick}>
|
onClick={this.onRotateClockwiseClick}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_download"
|
className="mx_ImageView_button mx_ImageView_button_download"
|
||||||
title={_t("Download")}
|
title={_t("Download")}
|
||||||
onClick={this.onDownloadClick}>
|
onClick={this.onDownloadClick}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
{ contextMenuButton }
|
{ contextMenuButton }
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_close"
|
className="mx_ImageView_button mx_ImageView_button_close"
|
||||||
title={_t("Close")}
|
title={_t("Close")}
|
||||||
onClick={this.props.onFinished}>
|
onClick={this.props.onFinished}
|
||||||
</AccessibleTooltipButton>
|
/>
|
||||||
{ this.renderContextMenu() }
|
{ this.renderContextMenu() }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -92,7 +92,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
|
||||||
<div className="mx_MiniAvatarUploader_indicator">
|
<div className="mx_MiniAvatarUploader_indicator">
|
||||||
{ busy ?
|
{ busy ?
|
||||||
<Spinner w={20} h={20} /> :
|
<Spinner w={20} h={20} /> :
|
||||||
<div className="mx_MiniAvatarUploader_cameraIcon"></div> }
|
<div className="mx_MiniAvatarUploader_cameraIcon" /> }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={classNames("mx_Tooltip", {
|
<div className={classNames("mx_Tooltip", {
|
||||||
|
|
|
@ -258,7 +258,10 @@ class Pill extends React.Component {
|
||||||
linkText = groupId;
|
linkText = groupId;
|
||||||
if (this.props.shouldShowPillAvatar) {
|
if (this.props.shouldShowPillAvatar) {
|
||||||
avatar = <BaseAvatar
|
avatar = <BaseAvatar
|
||||||
name={name || groupId} width={16} height={16} aria-hidden="true"
|
name={name || groupId}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
aria-hidden="true"
|
||||||
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />;
|
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />;
|
||||||
}
|
}
|
||||||
pillClass = 'mx_GroupPill';
|
pillClass = 'mx_GroupPill';
|
||||||
|
|
|
@ -134,8 +134,10 @@ export default class PowerSelector extends React.Component {
|
||||||
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
|
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
|
||||||
if (this.state.custom) {
|
if (this.state.custom) {
|
||||||
picker = (
|
picker = (
|
||||||
<Field type="number"
|
<Field
|
||||||
label={label} max={this.props.maxValue}
|
type="number"
|
||||||
|
label={label}
|
||||||
|
max={this.props.maxValue}
|
||||||
onBlur={this.onCustomBlur}
|
onBlur={this.onCustomBlur}
|
||||||
onKeyDown={this.onCustomKeyDown}
|
onKeyDown={this.onCustomKeyDown}
|
||||||
onChange={this.onCustomChange}
|
onChange={this.onCustomChange}
|
||||||
|
@ -157,9 +159,12 @@ export default class PowerSelector extends React.Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
picker = (
|
picker = (
|
||||||
<Field element="select"
|
<Field
|
||||||
label={label} onChange={this.onSelectChange}
|
element="select"
|
||||||
value={String(this.state.selectValue)} disabled={this.props.disabled}
|
label={label}
|
||||||
|
onChange={this.onSelectChange}
|
||||||
|
value={String(this.state.selectValue)}
|
||||||
|
disabled={this.props.disabled}
|
||||||
>
|
>
|
||||||
{ options }
|
{ options }
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import StyledRadioButton from "./StyledRadioButton";
|
import StyledRadioButton from "./StyledRadioButton";
|
||||||
|
|
||||||
interface IDefinition<T extends string> {
|
export interface IDefinition<T extends string> {
|
||||||
value: T;
|
value: T;
|
||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label: React.ReactChild;
|
label: ReactNode;
|
||||||
description?: React.ReactChild;
|
description?: ReactNode;
|
||||||
checked?: boolean; // If provided it will override the value comparison done in the group
|
checked?: boolean; // If provided it will override the value comparison done in the group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ function StyledRadioGroup<T extends string>({
|
||||||
checked={d.checked !== undefined ? d.checked : d.value === value}
|
checked={d.checked !== undefined ? d.checked : d.value === value}
|
||||||
name={name}
|
name={name}
|
||||||
value={d.value}
|
value={d.value}
|
||||||
disabled={disabled || d.disabled}
|
disabled={d.disabled ?? disabled}
|
||||||
outlined={outlined}
|
outlined={outlined}
|
||||||
>
|
>
|
||||||
{ d.label }
|
{ d.label }
|
||||||
|
|
|
@ -166,8 +166,7 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
public render() {
|
public render() {
|
||||||
// Render a placeholder
|
// Render a placeholder
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div className={this.props.className} />
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,14 +101,16 @@ class Category extends React.PureComponent<IProps> {
|
||||||
{ name }
|
{ name }
|
||||||
</h2>
|
</h2>
|
||||||
<LazyRenderList
|
<LazyRenderList
|
||||||
element="ul" className="mx_EmojiPicker_list"
|
element="ul"
|
||||||
itemHeight={EMOJI_HEIGHT} items={rows}
|
className="mx_EmojiPicker_list"
|
||||||
|
itemHeight={EMOJI_HEIGHT}
|
||||||
|
items={rows}
|
||||||
scrollTop={localScrollTop}
|
scrollTop={localScrollTop}
|
||||||
height={localHeight}
|
height={localHeight}
|
||||||
overflowItems={OVERFLOW_ROWS}
|
overflowItems={OVERFLOW_ROWS}
|
||||||
overflowMargin={0}
|
overflowMargin={0}
|
||||||
renderItem={this.renderEmojiRow}>
|
renderItem={this.renderEmojiRow}
|
||||||
</LazyRenderList>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,10 +86,16 @@ export default class GroupMemberList extends React.Component {
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||||
return (
|
return (
|
||||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
<EntityTile
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={this._showFullMemberList} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={this._showFullMemberList}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,7 +158,9 @@ export default class GroupMemberList extends React.Component {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
|
return <TruncatedList
|
||||||
|
className="mx_MemberList_wrapper"
|
||||||
|
truncateAt={this.state.truncateAt}
|
||||||
createOverflowElement={this._createOverflowTile}
|
createOverflowElement={this._createOverflowTile}
|
||||||
>
|
>
|
||||||
{ memberTiles }
|
{ memberTiles }
|
||||||
|
|
|
@ -56,14 +56,19 @@ export default class GroupMemberTile extends React.Component {
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
name={this.props.member.displayname || this.props.member.userId}
|
name={this.props.member.displayname || this.props.member.userId}
|
||||||
idName={this.props.member.userId}
|
idName={this.props.member.userId}
|
||||||
width={36} height={36}
|
width={36}
|
||||||
|
height={36}
|
||||||
url={avatarUrl}
|
url={avatarUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityTile name={name} avatarJsx={av} onClick={this.onClick}
|
<EntityTile
|
||||||
suppressOnHover={true} presenceState="online"
|
name={name}
|
||||||
|
avatarJsx={av}
|
||||||
|
onClick={this.onClick}
|
||||||
|
suppressOnHover={true}
|
||||||
|
presenceState="online"
|
||||||
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
|
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -76,10 +76,16 @@ export default class GroupRoomList extends React.Component {
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||||
return (
|
return (
|
||||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
<EntityTile
|
||||||
|
className="mx_EntityTile_ellipsis"
|
||||||
|
avatarJsx={
|
||||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||||
} name={text} presenceState="online" suppressOnHover={true}
|
}
|
||||||
onClick={this._showFullRoomList} />
|
name={text}
|
||||||
|
presenceState="online"
|
||||||
|
suppressOnHover={true}
|
||||||
|
onClick={this._showFullRoomList}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -142,7 +148,8 @@ export default class GroupRoomList extends React.Component {
|
||||||
}
|
}
|
||||||
const inputBox = (
|
const inputBox = (
|
||||||
<input
|
<input
|
||||||
className="mx_GroupRoomList_query mx_textinput" id="mx_GroupRoomList_query"
|
className="mx_GroupRoomList_query mx_textinput"
|
||||||
|
id="mx_GroupRoomList_query"
|
||||||
type="text"
|
type="text"
|
||||||
onChange={this.onSearchQueryChanged}
|
onChange={this.onSearchQueryChanged}
|
||||||
value={this.state.searchQuery}
|
value={this.state.searchQuery}
|
||||||
|
@ -156,8 +163,11 @@ export default class GroupRoomList extends React.Component {
|
||||||
<div className="mx_GroupRoomList" role="tabpanel">
|
<div className="mx_GroupRoomList" role="tabpanel">
|
||||||
{ inviteButton }
|
{ inviteButton }
|
||||||
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
|
<AutoHideScrollbar className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
|
||||||
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
|
<TruncatedList
|
||||||
createOverflowElement={this._createOverflowTile}>
|
className="mx_GroupRoomList_wrapper"
|
||||||
|
truncateAt={this.state.truncateAt}
|
||||||
|
createOverflowElement={this._createOverflowTile}
|
||||||
|
>
|
||||||
{ this.makeGroupRoomTiles(this.state.searchQuery) }
|
{ this.makeGroupRoomTiles(this.state.searchQuery) }
|
||||||
</TruncatedList>
|
</TruncatedList>
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
|
|
|
@ -48,8 +48,10 @@ class GroupRoomTile extends React.Component {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const av = (
|
const av = (
|
||||||
<BaseAvatar name={this.props.groupRoom.displayname}
|
<BaseAvatar
|
||||||
width={36} height={36}
|
name={this.props.groupRoom.displayname}
|
||||||
|
width={36}
|
||||||
|
height={36}
|
||||||
url={avatarUrl}
|
url={avatarUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -206,7 +206,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
|
||||||
{ sender }
|
{ sender }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CallEvent_type">
|
<div className="mx_CallEvent_type">
|
||||||
<div className="mx_CallEvent_type_icon"></div>
|
<div className="mx_CallEvent_type_icon" />
|
||||||
{ callType }
|
{ callType }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,14 +16,14 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { Playback } from "../../../voice/Playback";
|
import { Playback } from "../../../audio/Playback";
|
||||||
import InlineSpinner from '../elements/InlineSpinner';
|
import InlineSpinner from '../elements/InlineSpinner';
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AudioPlayer from "../audio_messages/AudioPlayer";
|
import AudioPlayer from "../audio_messages/AudioPlayer";
|
||||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
||||||
import MFileBody from "./MFileBody";
|
import MFileBody from "./MFileBody";
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
import { PlaybackManager } from "../../../voice/PlaybackManager";
|
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
error?: Error;
|
error?: Error;
|
||||||
|
@ -76,7 +76,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
// TODO: @@TR: Verify error state
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MAudioBody">
|
<span className="mx_MAudioBody">
|
||||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||||
|
@ -86,7 +85,6 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.playback) {
|
if (!this.state.playback) {
|
||||||
// TODO: @@TR: Verify loading/decrypting state
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MAudioBody">
|
<span className="mx_MAudioBody">
|
||||||
<InlineSpinner />
|
<InlineSpinner />
|
||||||
|
|
|
@ -306,7 +306,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
imageElement = <HiddenImagePlaceholder />;
|
imageElement = <HiddenImagePlaceholder />;
|
||||||
} else {
|
} else {
|
||||||
imageElement = (
|
imageElement = (
|
||||||
<img style={{ display: 'none' }} src={thumbUrl} ref={this.image}
|
<img
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
src={thumbUrl}
|
||||||
|
ref={this.image}
|
||||||
alt={content.body}
|
alt={content.body}
|
||||||
onError={this.onImageError}
|
onError={this.onImageError}
|
||||||
onLoad={this.onImageLoad}
|
onLoad={this.onImageLoad}
|
||||||
|
@ -340,8 +343,11 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
// which has the same width as the timeline
|
// which has the same width as the timeline
|
||||||
// mx_MImageBody_thumbnail resizes img to exactly container size
|
// mx_MImageBody_thumbnail resizes img to exactly container size
|
||||||
img = (
|
img = (
|
||||||
<img className="mx_MImageBody_thumbnail" src={thumbUrl} ref={this.image}
|
<img
|
||||||
style={{ maxWidth: maxWidth + "px" }}
|
className="mx_MImageBody_thumbnail"
|
||||||
|
src={thumbUrl}
|
||||||
|
ref={this.image}
|
||||||
|
style={{ maxWidth: `min(100%, ${maxWidth}px)` }}
|
||||||
alt={content.body}
|
alt={content.body}
|
||||||
onError={this.onImageError}
|
onError={this.onImageError}
|
||||||
onLoad={this.onImageLoad}
|
onLoad={this.onImageLoad}
|
||||||
|
@ -360,12 +366,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const thumbnail = (
|
const thumbnail = (
|
||||||
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px", maxWidth: maxWidth + "px" }} >
|
<div className="mx_MImageBody_thumbnail_container" style={{ maxHeight: maxHeight + "px", maxWidth: maxWidth + "px" }}>
|
||||||
{ showPlaceholder &&
|
{ showPlaceholder &&
|
||||||
<div className="mx_MImageBody_thumbnail" style={{
|
<div
|
||||||
|
className="mx_MImageBody_thumbnail"
|
||||||
|
style={{
|
||||||
// Constrain width here so that spinner appears central to the loaded thumbnail
|
// Constrain width here so that spinner appears central to the loaded thumbnail
|
||||||
maxWidth: infoWidth + "px",
|
maxWidth: `min(100%, ${infoWidth}px)`,
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{ placeholder }
|
{ placeholder }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -416,10 +425,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
|
|
||||||
if (this.state.error !== null) {
|
if (this.state.error !== null) {
|
||||||
return (
|
return (
|
||||||
<span className="mx_MImageBody">
|
<div className="mx_MImageBody">
|
||||||
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
<img src={require("../../../../res/img/warning.svg")} width="16" height="16" />
|
||||||
{ _t("Error decrypting image") }
|
{ _t("Error decrypting image") }
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -434,10 +443,10 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
const thumbnail = this.messageContent(contentUrl, thumbUrl, content);
|
const thumbnail = this.messageContent(contentUrl, thumbUrl, content);
|
||||||
const fileBody = this.getFileBody();
|
const fileBody = this.getFileBody();
|
||||||
|
|
||||||
return <span className="mx_MImageBody">
|
return <div className="mx_MImageBody">
|
||||||
{ thumbnail }
|
{ thumbnail }
|
||||||
{ fileBody }
|
{ fileBody }
|
||||||
</span>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,7 +461,7 @@ export class HiddenImagePlaceholder extends React.PureComponent<PlaceholderIProp
|
||||||
let className = 'mx_HiddenImagePlaceholder';
|
let className = 'mx_HiddenImagePlaceholder';
|
||||||
if (this.props.hover) className += ' mx_HiddenImagePlaceholder_hover';
|
if (this.props.hover) className += ' mx_HiddenImagePlaceholder_hover';
|
||||||
return (
|
return (
|
||||||
<div className={className} style={{ maxWidth: maxWidth }}>
|
<div className={className} style={{ maxWidth: `min(100%, ${maxWidth}px)` }}>
|
||||||
<div className='mx_HiddenImagePlaceholder_button'>
|
<div className='mx_HiddenImagePlaceholder_button'>
|
||||||
<span className='mx_HiddenImagePlaceholder_eye' />
|
<span className='mx_HiddenImagePlaceholder_eye' />
|
||||||
<span>{ _t("Show image") }</span>
|
<span>{ _t("Show image") }</span>
|
||||||
|
|
|
@ -267,8 +267,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||||
width={width}
|
width={width}
|
||||||
poster={poster}
|
poster={poster}
|
||||||
onPlay={this.videoOnPlay}
|
onPlay={this.videoOnPlay}
|
||||||
>
|
/>
|
||||||
</video>
|
|
||||||
{ this.props.tileShape && <MFileBody {...this.props} showGenericPlaceholder={false} /> }
|
{ this.props.tileShape && <MFileBody {...this.props} showGenericPlaceholder={false} /> }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue