Merge branch 'develop' into gsouquet/fix-18144
This commit is contained in:
commit
375f7b670a
43 changed files with 1713 additions and 737 deletions
|
@ -67,7 +67,6 @@
|
||||||
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
|
@import "./views/dialogs/_AddExistingToSpaceDialog.scss";
|
||||||
@import "./views/dialogs/_AddressPickerDialog.scss";
|
@import "./views/dialogs/_AddressPickerDialog.scss";
|
||||||
@import "./views/dialogs/_Analytics.scss";
|
@import "./views/dialogs/_Analytics.scss";
|
||||||
@import "./views/dialogs/_BetaFeedbackDialog.scss";
|
|
||||||
@import "./views/dialogs/_BugReportDialog.scss";
|
@import "./views/dialogs/_BugReportDialog.scss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||||
|
@ -76,16 +75,20 @@
|
||||||
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
|
@import "./views/dialogs/_CreateSubspaceDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_FeedbackDialog.scss";
|
@import "./views/dialogs/_FeedbackDialog.scss";
|
||||||
@import "./views/dialogs/_ForwardDialog.scss";
|
@import "./views/dialogs/_ForwardDialog.scss";
|
||||||
|
@import "./views/dialogs/_GenericFeatureFeedbackDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_HostSignupDialog.scss";
|
@import "./views/dialogs/_HostSignupDialog.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
|
@import "./views/dialogs/_JoinRuleDropdown.scss";
|
||||||
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
|
||||||
|
@import "./views/dialogs/_LeaveSpaceDialog.scss";
|
||||||
@import "./views/dialogs/_ManageRestrictedJoinRuleDialog.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";
|
||||||
|
|
|
@ -61,6 +61,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_link {
|
.mx_AccessibleButton_kind_link {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
|
|
|
@ -335,24 +335,17 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
> hr {
|
|
||||||
border: none;
|
|
||||||
height: 1px;
|
|
||||||
background-color: $groupFilterPanel-bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
margin: 0 0 20px;
|
margin: 0 0 20px;
|
||||||
flex: 0;
|
flex: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceFeedbackPrompt {
|
.mx_SpaceFeedbackPrompt {
|
||||||
margin-bottom: 16px;
|
padding: 7px; // 8px - 1px border
|
||||||
|
border: 1px solid $menu-border-color;
|
||||||
// hide the HR as we have our own
|
border-radius: 8px;
|
||||||
& + hr {
|
width: max-content;
|
||||||
display: none;
|
margin: 0 0 -40px auto; // collapse its own height to not push other components down
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_list {
|
.mx_SpaceRoomDirectory_list {
|
||||||
|
@ -513,66 +506,3 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceFeedbackPrompt {
|
|
||||||
margin-top: 18px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
> hr {
|
|
||||||
border: none;
|
|
||||||
border-top: 1px solid $input-border-color;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 32px;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
margin-right: auto;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 2px;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
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_AccessibleButton_kind_link {
|
|
||||||
color: $accent-color;
|
|
||||||
position: relative;
|
|
||||||
padding: 0 0 0 24px;
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: inherit;
|
|
||||||
line-height: inherit;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
background-color: $accent-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
mask-image: url('$(res)/img/element-icons/chat-bubbles.svg');
|
|
||||||
mask-position: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -99,6 +99,10 @@ limitations under the License.
|
||||||
.mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label {
|
.mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label {
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,64 +50,11 @@ limitations under the License.
|
||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpace_entry {
|
.mx_AccessibleButton_kind_link {
|
||||||
display: flex;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
// we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling
|
|
||||||
.mx_DecoratedRoomAvatar {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AddExistingToSpace_entry_name {
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: 30px;
|
|
||||||
flex-grow: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_Checkbox {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AddExistingToSpace_section_spaces {
|
|
||||||
.mx_BaseAvatar {
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AddExistingToSpace_section_experimental {
|
|
||||||
position: relative;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin: 12px 0;
|
|
||||||
padding: 8px 8px 8px 42px;
|
|
||||||
background-color: $header-panel-bg-color;
|
|
||||||
|
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
color: $secondary-fg-color;
|
margin-top: 8px;
|
||||||
|
padding: 0;
|
||||||
&::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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +152,12 @@ limitations under the License.
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: 80vh;
|
height: 80vh;
|
||||||
|
|
||||||
.mx_Dialog_title {
|
.mx_AddExistingToSpace {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SubspaceSelector {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
.mx_BaseAvatar_image {
|
||||||
|
@ -227,12 +179,6 @@ limitations under the License.
|
||||||
line-height: $font-22px;
|
line-height: $font-22px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog_onlySpace {
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dropdown_input {
|
.mx_Dropdown_input {
|
||||||
|
@ -252,7 +198,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dropdown_menu {
|
.mx_Dropdown_menu {
|
||||||
.mx_AddExistingToSpaceDialog_dropdownOptionActive {
|
.mx_SubspaceSelector_dropdownOptionActive {
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -273,9 +219,39 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AddExistingToSpace {
|
.mx_SubspaceSelector_onlySpace {
|
||||||
display: contents;
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_entry {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
.mx_DecoratedRoomAvatar, // we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling
|
||||||
|
.mx_BaseAvatar.mx_RoomAvatar_isSpaceRoom {
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.mx_RoomAvatar_isSpaceRoom,
|
||||||
|
.mx_RoomAvatar_isSpaceRoom img {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AddExistingToSpace_entry_name {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: 30px;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,56 +109,4 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
81
res/css/views/dialogs/_CreateSubspaceDialog.scss
Normal file
81
res/css/views/dialogs/_CreateSubspaceDialog.scss
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
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_CreateSubspaceDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSubspaceDialog {
|
||||||
|
width: 480px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
.mx_CreateSubspaceDialog_content {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.mx_CreateSubspaceDialog_betaNotice {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
.mx_BetaCard_betaPill {
|
||||||
|
margin-right: 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JoinRuleDropdown + p {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateSubspaceDialog_footer {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_CreateSubspaceDialog_footer_prompt {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_primary {
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 8px 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_BetaFeedbackDialog {
|
.mx_GenericFeatureFeedbackDialog {
|
||||||
.mx_BetaFeedbackDialog_subheading {
|
.mx_GenericFeatureFeedbackDialog_subheading {
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
line-height: $font-20px;
|
line-height: $font-20px;
|
67
res/css/views/dialogs/_JoinRuleDropdown.scss
Normal file
67
res/css/views/dialogs/_JoinRuleDropdown.scss
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
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_JoinRuleDropdown {
|
||||||
|
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_JoinRuleDropdown_invite::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/lock.svg');
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JoinRuleDropdown_public::before {
|
||||||
|
mask-image: url('$(res)/img/globe.svg');
|
||||||
|
mask-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JoinRuleDropdown_restricted::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
96
res/css/views/dialogs/_LeaveSpaceDialog.scss
Normal file
96
res/css/views/dialogs/_LeaveSpaceDialog.scss
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
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_LeaveSpaceDialog_wrapper {
|
||||||
|
.mx_Dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24px 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeaveSpaceDialog {
|
||||||
|
width: 440px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
max-height: 520px;
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.mx_RadioButton + .mx_RadioButton {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SearchBox {
|
||||||
|
// To match the space around the title
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeaveSpaceDialog_noResults {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeaveSpaceDialog_section {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_LeaveSpaceDialog_section_warning {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 12px 0 0;
|
||||||
|
padding: 12px 8px 12px 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> p {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_buttons {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mx_Dialog_primary {
|
||||||
|
background-color: $notice-primary-color !important; // override default colour
|
||||||
|
border-color: $notice-primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,8 +43,10 @@ limitations under the License.
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
mask-image: url('$(res)/img/feather-customised/minimise.svg');
|
mask-image: url('$(res)/img/feather-customised/minimise.svg');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover .mx_ViewSourceEvent_toggle {
|
.mx_EventTile:hover {
|
||||||
|
.mx_ViewSourceEvent_toggle {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,6 @@ limitations under the License.
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.mx_EventTile_selected {
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -48,10 +46,16 @@ limitations under the License.
|
||||||
left: -60px;
|
left: -60px;
|
||||||
right: -60px;
|
right: -60px;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
background: $eventbubble-bg-hover;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.mx_EventTile_selected {
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: $eventbubble-bg-hover;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
img {
|
img {
|
||||||
box-shadow: 0 0 0 3px $eventbubble-bg-hover;
|
box-shadow: 0 0 0 3px $eventbubble-bg-hover;
|
||||||
|
@ -267,7 +271,7 @@ limitations under the License.
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: start;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
|
@ -280,12 +284,22 @@ limitations under the License.
|
||||||
.mx_EventTile_info {
|
.mx_EventTile_info {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon {
|
||||||
|
margin-left: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line > a {
|
||||||
|
right: auto;
|
||||||
|
top: -15px;
|
||||||
|
left: -68px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventListSummary[data-layout=bubble] {
|
.mx_EventListSummary[data-layout=bubble] {
|
||||||
--maxWidth: 80%;
|
--maxWidth: 70%;
|
||||||
margin-left: calc(var(--avatarSize) + var(--gutterSize));
|
margin-left: calc(var(--avatarSize) + var(--gutterSize));
|
||||||
margin-right: calc(var(--gutterSize) + var(--avatarSize));
|
margin-right: 94px;
|
||||||
.mx_EventListSummary_toggle {
|
.mx_EventListSummary_toggle {
|
||||||
float: none;
|
float: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -59,7 +59,6 @@ $hover-select-border: 4px;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
display: inline-block; /* anti-zalgo, with overflow hidden */
|
display: inline-block; /* anti-zalgo, with overflow hidden */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -322,6 +321,10 @@ $hover-select-border: 4px;
|
||||||
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_bubbleContainer {
|
.mx_EventTile_bubbleContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 100px;
|
grid-template-columns: 1fr 100px;
|
||||||
|
|
|
@ -43,6 +43,12 @@ $spacePanelWidth: 71px;
|
||||||
color: $secondary-fg-color;
|
color: $secondary-fg-color;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceFeedbackPrompt {
|
||||||
|
border-top: 1px solid $input-border-color;
|
||||||
|
padding-top: 12px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX remove this when spaces leaves Beta
|
// XXX remove this when spaces leaves Beta
|
||||||
|
@ -99,3 +105,25 @@ $spacePanelWidth: 71px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceFeedbackPrompt {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
position: relative;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
color: $accent-color;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -80,6 +80,10 @@ export interface IProps extends IPosition {
|
||||||
managed?: boolean;
|
managed?: boolean;
|
||||||
wrapperClassName?: string;
|
wrapperClassName?: string;
|
||||||
|
|
||||||
|
// If true, this context menu will be mounted as a child to the parent container. Otherwise
|
||||||
|
// it will be mounted to a container at the root of the DOM.
|
||||||
|
mountAsChild?: boolean;
|
||||||
|
|
||||||
// Function to be called on menu close
|
// Function to be called on menu close
|
||||||
onFinished();
|
onFinished();
|
||||||
// on resize callback
|
// on resize callback
|
||||||
|
@ -390,8 +394,14 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.ReactChild {
|
render(): React.ReactChild {
|
||||||
|
if (this.props.mountAsChild) {
|
||||||
|
// Render as a child of the current parent
|
||||||
|
return this.renderMenu();
|
||||||
|
} else {
|
||||||
|
// Render as a child of a container at the root of the DOM
|
||||||
return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer());
|
return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
|
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { ReactNode, useMemo, useState } from "react";
|
import React, { ReactNode, useMemo, useState } from "react";
|
||||||
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 { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces";
|
import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -44,11 +43,13 @@ import { getChildOrder } from "../../stores/SpaceStore";
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import { linkifyElement } from "../../HtmlUtils";
|
import { linkifyElement } from "../../HtmlUtils";
|
||||||
import { getDisplayAliasForAliasSet } from "../../Rooms";
|
import { getDisplayAliasForAliasSet } from "../../Rooms";
|
||||||
|
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
|
||||||
interface IHierarchyProps {
|
interface IHierarchyProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
refreshToken?: any;
|
|
||||||
additionalButtons?: ReactNode;
|
additionalButtons?: ReactNode;
|
||||||
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
|
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
|
||||||
}
|
}
|
||||||
|
@ -315,18 +316,25 @@ export const HierarchyLevel = ({
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutate argument refreshToken to force a reload
|
export const useSpaceSummary = (space: Room): [
|
||||||
export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: any): [
|
|
||||||
null,
|
null,
|
||||||
ISpaceSummaryRoom[],
|
ISpaceSummaryRoom[],
|
||||||
Map<string, Map<string, ISpaceSummaryEvent>>?,
|
Map<string, Map<string, ISpaceSummaryEvent>>?,
|
||||||
Map<string, Set<string>>?,
|
Map<string, Set<string>>?,
|
||||||
Map<string, Set<string>>?,
|
Map<string, Set<string>>?,
|
||||||
] | [Error] => {
|
] | [Error] => {
|
||||||
|
// crude temporary refresh token approach until we have pagination and rework the data flow here
|
||||||
|
const [refreshToken, setRefreshToken] = useState(0);
|
||||||
|
useDispatcher(defaultDispatcher, (payload => {
|
||||||
|
if (payload.action === Action.UpdateSpaceHierarchy) {
|
||||||
|
setRefreshToken(t => t + 1);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// TODO pagination
|
// TODO pagination
|
||||||
return useAsyncMemo(async () => {
|
return useAsyncMemo(async () => {
|
||||||
try {
|
try {
|
||||||
const data = await cli.getSpaceSummary(space.roomId);
|
const data = await space.client.getSpaceSummary(space.roomId);
|
||||||
|
|
||||||
const parentChildRelations = new EnhancedMap<string, Map<string, ISpaceSummaryEvent>>();
|
const parentChildRelations = new EnhancedMap<string, Map<string, ISpaceSummaryEvent>>();
|
||||||
const childParentRelations = new EnhancedMap<string, Set<string>>();
|
const childParentRelations = new EnhancedMap<string, Set<string>>();
|
||||||
|
@ -354,7 +362,6 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
space,
|
space,
|
||||||
initialText = "",
|
initialText = "",
|
||||||
showRoom,
|
showRoom,
|
||||||
refreshToken,
|
|
||||||
additionalButtons,
|
additionalButtons,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -364,7 +371,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
|
|
||||||
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
||||||
|
|
||||||
const [summaryError, rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space, refreshToken);
|
const [summaryError, rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(space);
|
||||||
|
|
||||||
const roomsMap = useMemo(() => {
|
const roomsMap = useMemo(() => {
|
||||||
if (!rooms) return null;
|
if (!rooms) return null;
|
||||||
|
|
|
@ -47,13 +47,23 @@ import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||||
import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
import { useStateArray } from "../../hooks/useStateArray";
|
import { useStateArray } from "../../hooks/useStateArray";
|
||||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||||
import { shouldShowSpaceSettings, showAddExistingRooms, showCreateNewRoom, showSpaceSettings } from "../../utils/space";
|
import {
|
||||||
|
shouldShowSpaceSettings,
|
||||||
|
showAddExistingRooms,
|
||||||
|
showCreateNewRoom,
|
||||||
|
showCreateNewSubspace,
|
||||||
|
showSpaceSettings,
|
||||||
|
} from "../../utils/space";
|
||||||
import { showRoom, SpaceHierarchy } from "./SpaceRoomDirectory";
|
import { showRoom, SpaceHierarchy } from "./SpaceRoomDirectory";
|
||||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||||
import { useStateToggle } from "../../hooks/useStateToggle";
|
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
import FacePile from "../views/elements/FacePile";
|
import FacePile from "../views/elements/FacePile";
|
||||||
import { AddExistingToSpace } from "../views/dialogs/AddExistingToSpaceDialog";
|
import {
|
||||||
|
AddExistingToSpace,
|
||||||
|
defaultDmsRenderer,
|
||||||
|
defaultRoomsRenderer,
|
||||||
|
defaultSpacesRenderer,
|
||||||
|
} from "../views/dialogs/AddExistingToSpaceDialog";
|
||||||
import { ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
|
import { ChevronFace, ContextMenuButton, useContextMenu } from "./ContextMenu";
|
||||||
import IconizedContextMenu, {
|
import IconizedContextMenu, {
|
||||||
IconizedContextMenuOption,
|
IconizedContextMenuOption,
|
||||||
|
@ -62,10 +72,8 @@ import IconizedContextMenu, {
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import { BetaPill } from "../views/beta/BetaCard";
|
import { BetaPill } from "../views/beta/BetaCard";
|
||||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||||
import Modal from "../../Modal";
|
|
||||||
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
|
|
||||||
import SdkConfig from "../../SdkConfig";
|
|
||||||
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
import { EffectiveMembership, getEffectiveMembership } from "../../utils/membership";
|
||||||
|
import { SpaceFeedbackPrompt } from "../views/spaces/SpaceCreateMenu";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -92,28 +100,6 @@ enum Phase {
|
||||||
PrivateExistingRooms,
|
PrivateExistingRooms,
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Temporary for the Spaces Beta only
|
|
||||||
export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
|
||||||
if (!SdkConfig.get().bug_report_endpoint_url) return null;
|
|
||||||
|
|
||||||
return <div className="mx_SpaceFeedbackPrompt">
|
|
||||||
<hr />
|
|
||||||
<div>
|
|
||||||
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a beta feature.") }</span>
|
|
||||||
<AccessibleButton
|
|
||||||
kind="link"
|
|
||||||
onClick={() => {
|
|
||||||
if (onClick) onClick();
|
|
||||||
Modal.createTrackedDialog("Beta Feedback", "feature_spaces", BetaFeedbackDialog, {
|
|
||||||
featureId: "feature_spaces",
|
|
||||||
});
|
|
||||||
}}>
|
|
||||||
{ _t("Feedback") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const RoomMemberCount = ({ room, children }) => {
|
const RoomMemberCount = ({ room, children }) => {
|
||||||
const members = useRoomMembers(room);
|
const members = useRoomMembers(room);
|
||||||
const count = members.length;
|
const count = members.length;
|
||||||
|
@ -307,7 +293,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
const SpaceLandingAddButton = ({ space }) => {
|
||||||
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
|
||||||
|
|
||||||
let contextMenu;
|
let contextMenu;
|
||||||
|
@ -331,24 +317,32 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
|
||||||
if (await showCreateNewRoom(space)) {
|
if (await showCreateNewRoom(space)) {
|
||||||
onNewRoomAdded();
|
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add existing room")}
|
label={_t("Add existing room")}
|
||||||
iconClassName="mx_RoomList_iconHash"
|
iconClassName="mx_RoomList_iconHash"
|
||||||
onClick={async (e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
closeMenu();
|
closeMenu();
|
||||||
|
showAddExistingRooms(space);
|
||||||
const [added] = await showAddExistingRooms(space);
|
|
||||||
if (added) {
|
|
||||||
onNewRoomAdded();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("Add space")}
|
||||||
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
closeMenu();
|
||||||
|
showCreateNewSubspace(space);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BetaPill />
|
||||||
|
</IconizedContextMenuOption>
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
</IconizedContextMenu>;
|
</IconizedContextMenu>;
|
||||||
}
|
}
|
||||||
|
@ -389,11 +383,9 @@ const SpaceLanding = ({ space }) => {
|
||||||
|
|
||||||
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||||
|
|
||||||
const [refreshToken, forceUpdate] = useStateToggle(false);
|
|
||||||
|
|
||||||
let addRoomButton;
|
let addRoomButton;
|
||||||
if (canAddRooms) {
|
if (canAddRooms) {
|
||||||
addRoomButton = <SpaceLandingAddButton space={space} onNewRoomAdded={forceUpdate} />;
|
addRoomButton = <SpaceLandingAddButton space={space} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let settingsButton;
|
let settingsButton;
|
||||||
|
@ -416,6 +408,7 @@ const SpaceLanding = ({ space }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_landing">
|
return <div className="mx_SpaceRoomView_landing">
|
||||||
|
<SpaceFeedbackPrompt />
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
<div className="mx_SpaceRoomView_landing_name">
|
<div className="mx_SpaceRoomView_landing_name">
|
||||||
<RoomName room={space}>
|
<RoomName room={space}>
|
||||||
|
@ -440,15 +433,8 @@ const SpaceLanding = ({ space }) => {
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
</RoomTopic>
|
</RoomTopic>
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<SpaceHierarchy
|
<SpaceHierarchy space={space} showRoom={showRoom} additionalButtons={addRoomButton} />
|
||||||
space={space}
|
|
||||||
showRoom={showRoom}
|
|
||||||
refreshToken={refreshToken}
|
|
||||||
additionalButtons={addRoomButton}
|
|
||||||
/>
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -531,7 +517,6 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
value={buttonLabel}
|
value={buttonLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -550,11 +535,12 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
|
||||||
{ _t("Skip for now") }
|
{ _t("Skip for now") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
}
|
}
|
||||||
|
filterPlaceholder={_t("Search for rooms or spaces")}
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
|
roomsRenderer={defaultRoomsRenderer}
|
||||||
|
spacesRenderer={defaultSpacesRenderer}
|
||||||
|
dmsRenderer={defaultDmsRenderer}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons" />
|
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -574,7 +560,6 @@ const SpaceSetupPublicShare = ({ justCreatedOpts, space, onFinished, createdRoom
|
||||||
{ createdRooms ? _t("Go to my first room") : _t("Go to my space") }
|
{ createdRooms ? _t("Go to my first room") : _t("Go to my space") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -603,9 +588,8 @@ const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<div className="mx_SpaceRoomView_betaWarning">
|
<div className="mx_SpaceRoomView_betaWarning">
|
||||||
<h3>{ _t("Teammates might not be able to view or join any private rooms you make.") }</h3>
|
<h3>{ _t("Teammates might not be able to view or join any private rooms you make.") }</h3>
|
||||||
<p>{ _t("We're working on this as part of the beta, but just want to let you know.") }</p>
|
<p>{ _t("We're working on this, but just want to let you know.") }</p>
|
||||||
</div>
|
</div>
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -728,7 +712,6 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
value={buttonLabel}
|
value={buttonLabel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -103,8 +103,8 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetRecaptcha() {
|
private resetRecaptcha() {
|
||||||
if (this.captchaWidgetId !== null) {
|
if (this.captchaWidgetId) {
|
||||||
global.grecaptcha.reset(this.captchaWidgetId);
|
global?.grecaptcha?.reset(this.captchaWidgetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import SettingsFlag from "../elements/SettingsFlag";
|
import SettingsFlag from "../elements/SettingsFlag";
|
||||||
|
|
||||||
|
// XXX: Keep this around for re-use in future Betas
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
featureId: string;
|
featureId: string;
|
||||||
|
|
|
@ -90,10 +90,11 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
|
||||||
</MenuItemCheckbox>;
|
</MenuItemCheckbox>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({ label, iconClassName, ...props }) => {
|
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({ label, iconClassName, children, ...props }) => {
|
||||||
return <MenuItem {...props} label={label}>
|
return <MenuItem {...props} label={label}>
|
||||||
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
|
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
|
||||||
<span className="mx_IconizedContextMenu_label">{ label }</span>
|
<span className="mx_IconizedContextMenu_label">{ label }</span>
|
||||||
|
{ children }
|
||||||
</MenuItem>;
|
</MenuItem>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
67
src/components/views/dialogs/AddExistingSubspaceDialog.tsx
Normal file
67
src/components/views/dialogs/AddExistingSubspaceDialog.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
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, { useState } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import { AddExistingToSpace, defaultSpacesRenderer, SubspaceSelector } from "./AddExistingToSpaceDialog";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
onCreateSubspaceClick(): void;
|
||||||
|
onFinished(added?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddExistingSubspaceDialog: React.FC<IProps> = ({ space, onCreateSubspaceClick, onFinished }) => {
|
||||||
|
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={(
|
||||||
|
<SubspaceSelector
|
||||||
|
title={_t("Add existing space")}
|
||||||
|
space={space}
|
||||||
|
value={selectedSpace}
|
||||||
|
onChange={setSelectedSpace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
className="mx_AddExistingToSpaceDialog"
|
||||||
|
contentId="mx_AddExistingToSpace"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<MatrixClientContext.Provider value={space.client}>
|
||||||
|
<AddExistingToSpace
|
||||||
|
space={space}
|
||||||
|
onFinished={onFinished}
|
||||||
|
footerPrompt={<>
|
||||||
|
<div>{ _t("Want to add a new space instead?") }</div>
|
||||||
|
<AccessibleButton onClick={onCreateSubspaceClick} kind="link">
|
||||||
|
{ _t("Create a new space") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</>}
|
||||||
|
filterPlaceholder={_t("Search for spaces")}
|
||||||
|
spacesRenderer={defaultSpacesRenderer}
|
||||||
|
/>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddExistingSubspaceDialog;
|
||||||
|
|
|
@ -18,9 +18,9 @@ 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 { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import Dropdown from "../elements/Dropdown";
|
import Dropdown from "../elements/Dropdown";
|
||||||
import SearchBox from "../../structures/SearchBox";
|
import SearchBox from "../../structures/SearchBox";
|
||||||
|
@ -35,19 +35,20 @@ import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
|
||||||
import ProgressBar from "../elements/ProgressBar";
|
import ProgressBar from "../elements/ProgressBar";
|
||||||
import { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
|
|
||||||
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
import TruncatedList from "../elements/TruncatedList";
|
import TruncatedList from "../elements/TruncatedList";
|
||||||
import EntityTile from "../rooms/EntityTile";
|
import EntityTile from "../rooms/EntityTile";
|
||||||
import BaseAvatar from "../avatars/BaseAvatar";
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
onCreateRoomClick(space: Room): void;
|
onCreateRoomClick(): void;
|
||||||
|
onAddSubspaceClick(): void;
|
||||||
|
onFinished(added?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Entry = ({ room, checked, onChange }) => {
|
export const Entry = ({ room, checked, onChange }) => {
|
||||||
return <label className="mx_AddExistingToSpace_entry">
|
return <label className="mx_AddExistingToSpace_entry">
|
||||||
{ room?.isSpaceRoom()
|
{ room?.isSpaceRoom()
|
||||||
? <RoomAvatar room={room} height={32} width={32} />
|
? <RoomAvatar room={room} height={32} width={32} />
|
||||||
|
@ -65,14 +66,36 @@ const Entry = ({ room, checked, onChange }) => {
|
||||||
interface IAddExistingToSpaceProps {
|
interface IAddExistingToSpaceProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
footerPrompt?: ReactNode;
|
footerPrompt?: ReactNode;
|
||||||
|
filterPlaceholder: string;
|
||||||
emptySelectionButton?: ReactNode;
|
emptySelectionButton?: ReactNode;
|
||||||
onFinished(added: boolean): void;
|
onFinished(added: boolean): void;
|
||||||
|
roomsRenderer?(
|
||||||
|
rooms: Room[],
|
||||||
|
selectedToAdd: Set<Room>,
|
||||||
|
onChange: undefined | ((checked: boolean, room: Room) => void),
|
||||||
|
truncateAt: number,
|
||||||
|
overflowTile: (overflowCount: number, totalCount: number) => JSX.Element,
|
||||||
|
): ReactNode;
|
||||||
|
spacesRenderer?(
|
||||||
|
spaces: Room[],
|
||||||
|
selectedToAdd: Set<Room>,
|
||||||
|
onChange?: (checked: boolean, room: Room) => void,
|
||||||
|
): ReactNode;
|
||||||
|
dmsRenderer?(
|
||||||
|
dms: Room[],
|
||||||
|
selectedToAdd: Set<Room>,
|
||||||
|
onChange?: (checked: boolean, room: Room) => void,
|
||||||
|
): ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
space,
|
space,
|
||||||
footerPrompt,
|
footerPrompt,
|
||||||
emptySelectionButton,
|
emptySelectionButton,
|
||||||
|
filterPlaceholder,
|
||||||
|
roomsRenderer,
|
||||||
|
dmsRenderer,
|
||||||
|
spacesRenderer,
|
||||||
onFinished,
|
onFinished,
|
||||||
}) => {
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
@ -196,7 +219,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = !busy && !error ? (checked, room) => {
|
const onChange = !busy && !error ? (checked: boolean, room: Room) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
selectedToAdd.add(room);
|
selectedToAdd.add(room);
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,7 +229,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
const [truncateAt, setTruncateAt] = useState(20);
|
const [truncateAt, setTruncateAt] = useState(20);
|
||||||
function overflowTile(overflowCount, totalCount) {
|
function overflowTile(overflowCount: number, totalCount: number): JSX.Element {
|
||||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||||
return (
|
return (
|
||||||
<EntityTile
|
<EntityTile
|
||||||
|
@ -222,16 +245,49 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let noResults = true;
|
||||||
|
if ((roomsRenderer && rooms.length > 0) ||
|
||||||
|
(dmsRenderer && dms.length > 0) ||
|
||||||
|
(!roomsRenderer && !dmsRenderer && spacesRenderer && spaces.length > 0) // only count spaces when alone
|
||||||
|
) {
|
||||||
|
noResults = false;
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mx_AddExistingToSpace">
|
return <div className="mx_AddExistingToSpace">
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={_t("Filter your rooms and spaces")}
|
placeholder={filterPlaceholder}
|
||||||
onSearch={setQuery}
|
onSearch={setQuery}
|
||||||
autoComplete={true}
|
autoComplete={true}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
<AutoHideScrollbar className="mx_AddExistingToSpace_content">
|
<AutoHideScrollbar className="mx_AddExistingToSpace_content">
|
||||||
{ rooms.length > 0 ? (
|
{ rooms.length > 0 && roomsRenderer ? (
|
||||||
|
roomsRenderer(rooms, selectedToAdd, onChange, truncateAt, overflowTile)
|
||||||
|
) : undefined }
|
||||||
|
|
||||||
|
{ spaces.length > 0 && spacesRenderer ? (
|
||||||
|
spacesRenderer(spaces, selectedToAdd, onChange)
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
{ dms.length > 0 && dmsRenderer ? (
|
||||||
|
dmsRenderer(dms, selectedToAdd, onChange)
|
||||||
|
) : null }
|
||||||
|
|
||||||
|
{ noResults ? <span className="mx_AddExistingToSpace_noResults">
|
||||||
|
{ _t("No results") }
|
||||||
|
</span> : undefined }
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
|
||||||
|
<div className="mx_AddExistingToSpace_footer">
|
||||||
|
{ footer }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultRoomsRenderer: IAddExistingToSpaceProps["roomsRenderer"] = (
|
||||||
|
rooms, selectedToAdd, onChange, truncateAt, overflowTile,
|
||||||
|
) => (
|
||||||
<div className="mx_AddExistingToSpace_section">
|
<div className="mx_AddExistingToSpace_section">
|
||||||
<h3>{ _t("Rooms") }</h3>
|
<h3>{ _t("Rooms") }</h3>
|
||||||
<TruncatedList
|
<TruncatedList
|
||||||
|
@ -242,7 +298,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
checked={selectedToAdd.has(room)}
|
checked={selectedToAdd.has(room)}
|
||||||
onChange={onChange ? (checked) => {
|
onChange={onChange ? (checked: boolean) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
} : null}
|
} : null}
|
||||||
/>,
|
/>,
|
||||||
|
@ -250,15 +306,10 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
getChildCount={() => rooms.length}
|
getChildCount={() => rooms.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : undefined }
|
);
|
||||||
|
|
||||||
{ spaces.length > 0 ? (
|
export const defaultSpacesRenderer: IAddExistingToSpaceProps["spacesRenderer"] = (spaces, selectedToAdd, onChange) => (
|
||||||
<div className="mx_AddExistingToSpace_section mx_AddExistingToSpace_section_spaces">
|
<div className="mx_AddExistingToSpace_section">
|
||||||
<h3>{ _t("Spaces") }</h3>
|
|
||||||
<div className="mx_AddExistingToSpace_section_experimental">
|
|
||||||
<div>{ _t("Feeling experimental?") }</div>
|
|
||||||
<div>{ _t("You can add existing spaces to a space.") }</div>
|
|
||||||
</div>
|
|
||||||
{ spaces.map(space => {
|
{ spaces.map(space => {
|
||||||
return <Entry
|
return <Entry
|
||||||
key={space.roomId}
|
key={space.roomId}
|
||||||
|
@ -270,9 +321,9 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
/>;
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
) : null }
|
);
|
||||||
|
|
||||||
{ dms.length > 0 ? (
|
export const defaultDmsRenderer: IAddExistingToSpaceProps["dmsRenderer"] = (dms, selectedToAdd, onChange) => (
|
||||||
<div className="mx_AddExistingToSpace_section">
|
<div className="mx_AddExistingToSpace_section">
|
||||||
<h3>{ _t("Direct Messages") }</h3>
|
<h3>{ _t("Direct Messages") }</h3>
|
||||||
{ dms.map(room => {
|
{ dms.map(room => {
|
||||||
|
@ -280,69 +331,80 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
key={room.roomId}
|
key={room.roomId}
|
||||||
room={room}
|
room={room}
|
||||||
checked={selectedToAdd.has(room)}
|
checked={selectedToAdd.has(room)}
|
||||||
onChange={onChange ? (checked) => {
|
onChange={onChange ? (checked: boolean) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
} : null}
|
} : null}
|
||||||
/>;
|
/>;
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
) : null }
|
);
|
||||||
|
|
||||||
{ spaces.length + rooms.length + dms.length < 1 ? <span className="mx_AddExistingToSpace_noResults">
|
interface ISubspaceSelectorProps {
|
||||||
{ _t("No results") }
|
title: string;
|
||||||
</span> : undefined }
|
space: Room;
|
||||||
</AutoHideScrollbar>
|
value: Room;
|
||||||
|
onChange(space: Room): void;
|
||||||
|
}
|
||||||
|
|
||||||
<div className="mx_AddExistingToSpace_footer">
|
export const SubspaceSelector = ({ title, space, value, onChange }: ISubspaceSelectorProps) => {
|
||||||
{ footer }
|
const options = useMemo(() => {
|
||||||
</div>
|
return [space, ...SpaceStore.instance.getChildSpaces(space.roomId).filter(space => {
|
||||||
</div>;
|
return space.currentState.maySendStateEvent(EventType.SpaceChild, space.client.credentials.userId);
|
||||||
};
|
})];
|
||||||
|
}, [space]);
|
||||||
|
|
||||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onFinished }) => {
|
let body;
|
||||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
if (options.length > 1) {
|
||||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
body = (
|
||||||
|
<Dropdown
|
||||||
let spaceOptionSection;
|
id="mx_SpaceSelectDropdown"
|
||||||
if (existingSubspaces.length > 0) {
|
className="mx_SpaceSelectDropdown"
|
||||||
const options = [space, ...existingSubspaces].map((space) => {
|
onOptionChange={(key: string) => {
|
||||||
const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", {
|
onChange(options.find(space => space.roomId === key) || space);
|
||||||
mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace,
|
}}
|
||||||
|
value={value.roomId}
|
||||||
|
label={_t("Space selection")}
|
||||||
|
>
|
||||||
|
{ options.map((space) => {
|
||||||
|
const classes = classNames({
|
||||||
|
mx_SubspaceSelector_dropdownOptionActive: space === value,
|
||||||
});
|
});
|
||||||
return <div key={space.roomId} className={classes}>
|
return <div key={space.roomId} className={classes}>
|
||||||
<RoomAvatar room={space} width={24} height={24} />
|
<RoomAvatar room={space} width={24} height={24} />
|
||||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||||
</div>;
|
</div>;
|
||||||
});
|
}) }
|
||||||
|
|
||||||
spaceOptionSection = (
|
|
||||||
<Dropdown
|
|
||||||
id="mx_SpaceSelectDropdown"
|
|
||||||
onOptionChange={(key: string) => {
|
|
||||||
setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space);
|
|
||||||
}}
|
|
||||||
value={selectedSpace.roomId}
|
|
||||||
label={_t("Space selection")}
|
|
||||||
>
|
|
||||||
{ options }
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
spaceOptionSection = <div className="mx_AddExistingToSpaceDialog_onlySpace">
|
body = (
|
||||||
|
<div className="mx_SubspaceSelector_onlySpace">
|
||||||
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
{ space.name || getDisplayAliasForRoom(space) || space.roomId }
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = <React.Fragment>
|
return <div className="mx_SubspaceSelector">
|
||||||
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
<RoomAvatar room={value} height={40} width={40} />
|
||||||
<div>
|
<div>
|
||||||
<h1>{ _t("Add existing rooms") }</h1>
|
<h1>{ title }</h1>
|
||||||
{ spaceOptionSection }
|
{ body }
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>;
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onAddSubspaceClick, onFinished }) => {
|
||||||
|
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||||
|
|
||||||
return <BaseDialog
|
return <BaseDialog
|
||||||
title={title}
|
title={(
|
||||||
|
<SubspaceSelector
|
||||||
|
title={_t("Add existing rooms")}
|
||||||
|
space={space}
|
||||||
|
value={selectedSpace}
|
||||||
|
onChange={setSelectedSpace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
className="mx_AddExistingToSpaceDialog"
|
className="mx_AddExistingToSpaceDialog"
|
||||||
contentId="mx_AddExistingToSpace"
|
contentId="mx_AddExistingToSpace"
|
||||||
onFinished={onFinished}
|
onFinished={onFinished}
|
||||||
|
@ -354,14 +416,35 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick,
|
||||||
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(space)} kind="link">
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
onCreateRoomClick();
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{ _t("Create a new room") }
|
{ _t("Create a new room") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</>}
|
</>}
|
||||||
|
filterPlaceholder={_t("Search for rooms")}
|
||||||
|
roomsRenderer={defaultRoomsRenderer}
|
||||||
|
spacesRenderer={() => (
|
||||||
|
<div className="mx_AddExistingToSpace_section">
|
||||||
|
<h3>{ _t("Spaces") }</h3>
|
||||||
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
onAddSubspaceClick();
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Adding spaces has moved.") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
dmsRenderer={defaultDmsRenderer}
|
||||||
/>
|
/>
|
||||||
</MatrixClientContext.Provider>
|
</MatrixClientContext.Provider>
|
||||||
|
|
||||||
<SpaceFeedbackPrompt onClick={() => onFinished(false)} />
|
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,22 +14,18 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
|
|
||||||
import QuestionDialog from './QuestionDialog';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Field from "../elements/Field";
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
import { IDialogProps } from "./IDialogProps";
|
import { IDialogProps } from "./IDialogProps";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
|
||||||
import Modal from "../../../Modal";
|
|
||||||
import InfoDialog from "./InfoDialog";
|
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { UserTab } from "./UserSettingsDialog";
|
import { UserTab } from "./UserSettingsDialog";
|
||||||
|
import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
|
||||||
|
|
||||||
|
// XXX: Keep this around for re-use in future Betas
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
featureId: string;
|
featureId: string;
|
||||||
|
@ -38,39 +34,15 @@ interface IProps extends IDialogProps {
|
||||||
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
const info = SettingsStore.getBetaInfo(featureId);
|
const info = SettingsStore.getBetaInfo(featureId);
|
||||||
|
|
||||||
const [comment, setComment] = useState("");
|
return <GenericFeatureFeedbackDialog
|
||||||
const [canContact, setCanContact] = useState(false);
|
|
||||||
|
|
||||||
const sendFeedback = async (ok: boolean) => {
|
|
||||||
if (!ok) return onFinished(false);
|
|
||||||
|
|
||||||
const extraData = SettingsStore.getBetaInfo(featureId)?.extraSettings.reduce((o, k) => {
|
|
||||||
o[k] = SettingsStore.getValue(k);
|
|
||||||
return o;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact, extraData);
|
|
||||||
onFinished(true);
|
|
||||||
|
|
||||||
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {
|
|
||||||
title: _t("Beta feedback"),
|
|
||||||
description: _t("Thank you for your feedback, we really appreciate it."),
|
|
||||||
button: _t("Done"),
|
|
||||||
hasCloseButton: false,
|
|
||||||
fixedWidth: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (<QuestionDialog
|
|
||||||
className="mx_BetaFeedbackDialog"
|
|
||||||
hasCancelButton={true}
|
|
||||||
title={_t("%(featureName)s beta feedback", { featureName: info.title })}
|
title={_t("%(featureName)s beta feedback", { featureName: info.title })}
|
||||||
description={<React.Fragment>
|
subheading={_t(info.feedbackSubheading)}
|
||||||
<div className="mx_BetaFeedbackDialog_subheading">
|
onFinished={onFinished}
|
||||||
{ _t(info.feedbackSubheading) }
|
rageshakeLabel={info.feedbackLabel}
|
||||||
|
rageshakeData={Object.fromEntries((SettingsStore.getBetaInfo(featureId)?.extraSettings || []).map(k => {
|
||||||
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
|
return SettingsStore.getValue(k);
|
||||||
|
}))}
|
||||||
|
>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="link"
|
kind="link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -83,32 +55,7 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
>
|
>
|
||||||
{ _t("To leave the beta, visit your settings.") }
|
{ _t("To leave the beta, visit your settings.") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</GenericFeatureFeedbackDialog>;
|
||||||
|
|
||||||
<Field
|
|
||||||
id="feedbackComment"
|
|
||||||
label={_t("Feedback")}
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
value={comment}
|
|
||||||
element="textarea"
|
|
||||||
onChange={(ev) => {
|
|
||||||
setComment(ev.target.value);
|
|
||||||
}}
|
|
||||||
autoFocus={true}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StyledCheckbox
|
|
||||||
checked={canContact}
|
|
||||||
onClick={e => setCanContact((e.target as HTMLInputElement).checked)}
|
|
||||||
>
|
|
||||||
{ _t("You may contact me if you have any follow up questions") }
|
|
||||||
</StyledCheckbox>
|
|
||||||
</React.Fragment>}
|
|
||||||
button={_t("Send feedback")}
|
|
||||||
buttonDisabled={!comment}
|
|
||||||
onFinished={sendFeedback}
|
|
||||||
/>);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BetaFeedbackDialog;
|
export default BetaFeedbackDialog;
|
||||||
|
|
|
@ -32,8 +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 Dropdown from "../elements/Dropdown";
|
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
defaultPublic?: boolean;
|
defaultPublic?: boolean;
|
||||||
|
@ -322,21 +322,6 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
title = this.state.joinRule === JoinRule.Public ? _t('Create a public room') : _t('Create a private room');
|
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} title={title}>
|
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished} title={title}>
|
||||||
<form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
|
<form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
|
||||||
|
@ -356,16 +341,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
className="mx_CreateRoomDialog_topic"
|
className="mx_CreateRoomDialog_topic"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Dropdown
|
<JoinRuleDropdown
|
||||||
id="mx_CreateRoomDialog_typeDropdown"
|
|
||||||
className="mx_CreateRoomDialog_typeDropdown"
|
|
||||||
onOptionChange={this.onJoinRuleChange}
|
|
||||||
menuWidth={448}
|
|
||||||
value={this.state.joinRule}
|
|
||||||
label={_t("Room visibility")}
|
label={_t("Room visibility")}
|
||||||
>
|
labelInvite={_t("Private room (invite only)")}
|
||||||
{ options }
|
labelPublic={_t("Public room")}
|
||||||
</Dropdown>
|
labelRestricted={this.supportsRestricted ? _t("Visible to space members") : undefined}
|
||||||
|
value={this.state.joinRule}
|
||||||
|
onChange={this.onJoinRuleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
{ publicPrivateLabel }
|
{ publicPrivateLabel }
|
||||||
{ e2eeSection }
|
{ e2eeSection }
|
||||||
|
|
210
src/components/views/dialogs/CreateSubspaceDialog.tsx
Normal file
210
src/components/views/dialogs/CreateSubspaceDialog.tsx
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
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, { useRef, useState } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { JoinRule, Preset } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import { BetaPill } from "../beta/BetaCard";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import RoomAliasField from "../elements/RoomAliasField";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import { SpaceCreateForm } from "../spaces/SpaceCreateMenu";
|
||||||
|
import createRoom from "../../../createRoom";
|
||||||
|
import { SubspaceSelector } from "./AddExistingToSpaceDialog";
|
||||||
|
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
onAddExistingSpaceClick(): void;
|
||||||
|
onFinished(added?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick, onFinished }) => {
|
||||||
|
const [parentSpace, setParentSpace] = useState(space);
|
||||||
|
|
||||||
|
const [busy, setBusy] = useState<boolean>(false);
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const spaceNameField = useRef<Field>();
|
||||||
|
const [alias, setAlias] = useState("");
|
||||||
|
const spaceAliasField = useRef<RoomAliasField>();
|
||||||
|
const [avatar, setAvatar] = useState<File>(null);
|
||||||
|
const [topic, setTopic] = useState<string>("");
|
||||||
|
|
||||||
|
const supportsRestricted = !!SpaceStore.instance.restrictedJoinRuleSupport?.preferred;
|
||||||
|
|
||||||
|
const spaceJoinRule = space.getJoinRule();
|
||||||
|
let defaultJoinRule = JoinRule.Invite;
|
||||||
|
if (spaceJoinRule === JoinRule.Public) {
|
||||||
|
defaultJoinRule = JoinRule.Public;
|
||||||
|
} else if (supportsRestricted) {
|
||||||
|
defaultJoinRule = JoinRule.Restricted;
|
||||||
|
}
|
||||||
|
const [joinRule, setJoinRule] = useState<JoinRule>(defaultJoinRule);
|
||||||
|
|
||||||
|
const onCreateSubspaceClick = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
|
|
||||||
|
setBusy(true);
|
||||||
|
// require & validate the space name field
|
||||||
|
if (!await spaceNameField.current.validate({ allowEmpty: false })) {
|
||||||
|
spaceNameField.current.focus();
|
||||||
|
spaceNameField.current.validate({ allowEmpty: false, focused: true });
|
||||||
|
setBusy(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// validate the space name alias field but do not require it
|
||||||
|
if (joinRule === JoinRule.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
|
||||||
|
spaceAliasField.current.focus();
|
||||||
|
spaceAliasField.current.validate({ allowEmpty: true, focused: true });
|
||||||
|
setBusy(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createRoom({
|
||||||
|
createOpts: {
|
||||||
|
preset: joinRule === JoinRule.Public ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
|
name,
|
||||||
|
power_level_content_override: {
|
||||||
|
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
||||||
|
events_default: 100,
|
||||||
|
...joinRule === JoinRule.Public ? { invite: 0 } : {},
|
||||||
|
},
|
||||||
|
room_alias_name: joinRule === JoinRule.Public && alias
|
||||||
|
? alias.substr(1, alias.indexOf(":") - 1)
|
||||||
|
: undefined,
|
||||||
|
topic,
|
||||||
|
},
|
||||||
|
avatar,
|
||||||
|
roomType: RoomType.Space,
|
||||||
|
parentSpace,
|
||||||
|
spinner: false,
|
||||||
|
encryption: false,
|
||||||
|
andView: true,
|
||||||
|
inlineErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
onFinished(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let joinRuleMicrocopy: JSX.Element;
|
||||||
|
if (joinRule === JoinRule.Restricted) {
|
||||||
|
joinRuleMicrocopy = <p>
|
||||||
|
{ _t(
|
||||||
|
"Anyone in <SpaceName/> will be able to find and join.", {}, {
|
||||||
|
SpaceName: () => <b>{ parentSpace.name }</b>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
</p>;
|
||||||
|
} else if (joinRule === JoinRule.Public) {
|
||||||
|
joinRuleMicrocopy = <p>
|
||||||
|
{ _t(
|
||||||
|
"Anyone will be able to find and join this space, not just members of <SpaceName/>.", {}, {
|
||||||
|
SpaceName: () => <b>{ parentSpace.name }</b>,
|
||||||
|
},
|
||||||
|
) }
|
||||||
|
</p>;
|
||||||
|
} else if (joinRule === JoinRule.Invite) {
|
||||||
|
joinRuleMicrocopy = <p>
|
||||||
|
{ _t("Only people invited will be able to find and join this space.") }
|
||||||
|
</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={(
|
||||||
|
<SubspaceSelector
|
||||||
|
title={_t("Create a space")}
|
||||||
|
space={space}
|
||||||
|
value={parentSpace}
|
||||||
|
onChange={setParentSpace}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
className="mx_CreateSubspaceDialog"
|
||||||
|
contentId="mx_CreateSubspaceDialog"
|
||||||
|
onFinished={onFinished}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<MatrixClientContext.Provider value={space.client}>
|
||||||
|
<div className="mx_CreateSubspaceDialog_content">
|
||||||
|
<div className="mx_CreateSubspaceDialog_betaNotice">
|
||||||
|
<BetaPill />
|
||||||
|
{ _t("Add a space to a space you manage.") }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SpaceCreateForm
|
||||||
|
busy={busy}
|
||||||
|
onSubmit={onCreateSubspaceClick}
|
||||||
|
setAvatar={setAvatar}
|
||||||
|
name={name}
|
||||||
|
setName={setName}
|
||||||
|
nameFieldRef={spaceNameField}
|
||||||
|
topic={topic}
|
||||||
|
setTopic={setTopic}
|
||||||
|
alias={alias}
|
||||||
|
setAlias={setAlias}
|
||||||
|
showAliasField={joinRule === JoinRule.Public}
|
||||||
|
aliasFieldRef={spaceAliasField}
|
||||||
|
>
|
||||||
|
<JoinRuleDropdown
|
||||||
|
label={_t("Space visibility")}
|
||||||
|
labelInvite={_t("Private space (invite only)")}
|
||||||
|
labelPublic={_t("Public space")}
|
||||||
|
labelRestricted={supportsRestricted ? _t("Visible to space members") : undefined}
|
||||||
|
width={478}
|
||||||
|
value={joinRule}
|
||||||
|
onChange={setJoinRule}
|
||||||
|
/>
|
||||||
|
{ joinRuleMicrocopy }
|
||||||
|
</SpaceCreateForm>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx_CreateSubspaceDialog_footer">
|
||||||
|
<div className="mx_CreateSubspaceDialog_footer_prompt">
|
||||||
|
<div>{ _t("Want to add an existing space instead?") }</div>
|
||||||
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
onAddExistingSpaceClick();
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Add existing space") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AccessibleButton kind="primary_outline" disabled={busy} onClick={() => onFinished(false)}>
|
||||||
|
{ _t("Cancel") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton kind="primary" disabled={busy} onClick={onCreateSubspaceClick}>
|
||||||
|
{ busy ? _t("Adding...") : _t("Add") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</MatrixClientContext.Provider>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateSubspaceDialog;
|
||||||
|
|
101
src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
Normal file
101
src/components/views/dialogs/GenericFeatureFeedbackDialog.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
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, { useState } from "react";
|
||||||
|
|
||||||
|
import QuestionDialog from './QuestionDialog';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
||||||
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import InfoDialog from "./InfoDialog";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
title: string;
|
||||||
|
subheading: string;
|
||||||
|
rageshakeLabel: string;
|
||||||
|
rageshakeData?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
||||||
|
title,
|
||||||
|
subheading,
|
||||||
|
children,
|
||||||
|
rageshakeLabel,
|
||||||
|
rageshakeData = {},
|
||||||
|
onFinished,
|
||||||
|
}) => {
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
const [canContact, setCanContact] = useState(false);
|
||||||
|
|
||||||
|
const sendFeedback = async (ok: boolean) => {
|
||||||
|
if (!ok) return onFinished(false);
|
||||||
|
|
||||||
|
submitFeedback(SdkConfig.get().bug_report_endpoint_url, rageshakeLabel, comment, canContact, rageshakeData);
|
||||||
|
onFinished(true);
|
||||||
|
|
||||||
|
Modal.createTrackedDialog("Feedback Sent", rageshakeLabel, InfoDialog, {
|
||||||
|
title,
|
||||||
|
description: _t("Thank you for your feedback, we really appreciate it."),
|
||||||
|
button: _t("Done"),
|
||||||
|
hasCloseButton: false,
|
||||||
|
fixedWidth: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (<QuestionDialog
|
||||||
|
className="mx_GenericFeatureFeedbackDialog"
|
||||||
|
hasCancelButton={true}
|
||||||
|
title={title}
|
||||||
|
description={<React.Fragment>
|
||||||
|
<div className="mx_GenericFeatureFeedbackDialog_subheading">
|
||||||
|
{ subheading }
|
||||||
|
|
||||||
|
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
|
||||||
|
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id="feedbackComment"
|
||||||
|
label={_t("Feedback")}
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
value={comment}
|
||||||
|
element="textarea"
|
||||||
|
onChange={(ev) => {
|
||||||
|
setComment(ev.target.value);
|
||||||
|
}}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StyledCheckbox
|
||||||
|
checked={canContact}
|
||||||
|
onChange={e => setCanContact((e.target as HTMLInputElement).checked)}
|
||||||
|
>
|
||||||
|
{ _t("You may contact me if you have any follow up questions") }
|
||||||
|
</StyledCheckbox>
|
||||||
|
</React.Fragment>}
|
||||||
|
button={_t("Send feedback")}
|
||||||
|
buttonDisabled={!comment}
|
||||||
|
onFinished={sendFeedback}
|
||||||
|
/>);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenericFeatureFeedbackDialog;
|
197
src/components/views/dialogs/LeaveSpaceDialog.tsx
Normal file
197
src/components/views/dialogs/LeaveSpaceDialog.tsx
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
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, { useEffect, useMemo, useState } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
|
import { Entry } from "./AddExistingToSpaceDialog";
|
||||||
|
import SearchBox from "../../structures/SearchBox";
|
||||||
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
|
import StyledRadioGroup from "../elements/StyledRadioGroup";
|
||||||
|
|
||||||
|
enum RoomsToLeave {
|
||||||
|
All = "All",
|
||||||
|
Specific = "Specific",
|
||||||
|
None = "None",
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
const filteredRooms = useMemo(() => {
|
||||||
|
if (!lcQuery) {
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matcher = new QueryMatcher<Room>(rooms, {
|
||||||
|
keys: ["name"],
|
||||||
|
funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
||||||
|
shouldMatchWordsOnly: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return matcher.match(lcQuery);
|
||||||
|
}, [rooms, lcQuery]);
|
||||||
|
|
||||||
|
return <div className="mx_LeaveSpaceDialog_section">
|
||||||
|
<SearchBox
|
||||||
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
|
placeholder={filterPlaceholder}
|
||||||
|
onSearch={setQuery}
|
||||||
|
autoComplete={true}
|
||||||
|
autoFocus={true}
|
||||||
|
/>
|
||||||
|
<AutoHideScrollbar className="mx_LeaveSpaceDialog_content">
|
||||||
|
{ filteredRooms.map(room => {
|
||||||
|
return <Entry
|
||||||
|
key={room.roomId}
|
||||||
|
room={room}
|
||||||
|
checked={selected.has(room)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
onChange(checked, room);
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}) }
|
||||||
|
{ filteredRooms.length < 1 ? <span className="mx_LeaveSpaceDialog_noResults">
|
||||||
|
{ _t("No results") }
|
||||||
|
</span> : undefined }
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
||||||
|
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||||
|
const [state, setState] = useState<string>(RoomsToLeave.All);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state === RoomsToLeave.All) {
|
||||||
|
setRoomsToLeave(spaceChildren);
|
||||||
|
} else {
|
||||||
|
setRoomsToLeave([]);
|
||||||
|
}
|
||||||
|
}, [setRoomsToLeave, state, spaceChildren]);
|
||||||
|
|
||||||
|
return <div className="mx_LeaveSpaceDialog_section">
|
||||||
|
<StyledRadioGroup
|
||||||
|
name="roomsToLeave"
|
||||||
|
value={state}
|
||||||
|
onChange={setState}
|
||||||
|
definitions={[
|
||||||
|
{
|
||||||
|
value: RoomsToLeave.All,
|
||||||
|
label: _t("Leave all rooms and spaces"),
|
||||||
|
}, {
|
||||||
|
value: RoomsToLeave.None,
|
||||||
|
label: _t("Don't leave any"),
|
||||||
|
}, {
|
||||||
|
value: RoomsToLeave.Specific,
|
||||||
|
label: _t("Leave specific rooms and spaces"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ state === RoomsToLeave.Specific && (
|
||||||
|
<SpaceChildPicker
|
||||||
|
filterPlaceholder={_t("Search %(spaceName)s", { spaceName: space.name })}
|
||||||
|
rooms={spaceChildren}
|
||||||
|
selected={selected}
|
||||||
|
onChange={(selected: boolean, room: Room) => {
|
||||||
|
if (selected) {
|
||||||
|
setRoomsToLeave([room, ...roomsToLeave]);
|
||||||
|
} else {
|
||||||
|
setRoomsToLeave(roomsToLeave.filter(r => r !== room));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
onFinished(leave: boolean, rooms?: Room[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOnlyAdmin = (room: Room): boolean => {
|
||||||
|
return !room.getJoinedMembers().some(member => {
|
||||||
|
return member.userId !== room.client.credentials.userId && member.powerLevelNorm === 100;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const LeaveSpaceDialog: React.FC<IProps> = ({ space, onFinished }) => {
|
||||||
|
const spaceChildren = useMemo(() => SpaceStore.instance.getChildren(space.roomId), [space.roomId]);
|
||||||
|
const [roomsToLeave, setRoomsToLeave] = useState<Room[]>([]);
|
||||||
|
|
||||||
|
let rejoinWarning;
|
||||||
|
if (space.getJoinRule() !== JoinRule.Public) {
|
||||||
|
rejoinWarning = _t("You won't be able to rejoin unless you are re-invited.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let onlyAdminWarning;
|
||||||
|
if (isOnlyAdmin(space)) {
|
||||||
|
onlyAdminWarning = _t("You're the only admin of this space. " +
|
||||||
|
"Leaving it will mean no one has control over it.");
|
||||||
|
} else {
|
||||||
|
const numChildrenOnlyAdminIn = roomsToLeave.filter(isOnlyAdmin).length;
|
||||||
|
if (numChildrenOnlyAdminIn > 0) {
|
||||||
|
onlyAdminWarning = _t("You're the only admin of some of the rooms or spaces you wish to leave. " +
|
||||||
|
"Leaving them will leave them without any admins.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <BaseDialog
|
||||||
|
title={_t("Leave %(spaceName)s", { spaceName: space.name })}
|
||||||
|
className="mx_LeaveSpaceDialog"
|
||||||
|
contentId="mx_LeaveSpaceDialog"
|
||||||
|
onFinished={() => onFinished(false)}
|
||||||
|
fixedWidth={false}
|
||||||
|
>
|
||||||
|
<div className="mx_Dialog_content" id="mx_LeaveSpaceDialog">
|
||||||
|
<p>
|
||||||
|
{ _t("Are you sure you want to leave <spaceName/>?", {}, {
|
||||||
|
spaceName: () => <b>{ space.name }</b>,
|
||||||
|
}) }
|
||||||
|
|
||||||
|
{ rejoinWarning }
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{ spaceChildren.length > 0 && <LeaveRoomsPicker
|
||||||
|
space={space}
|
||||||
|
spaceChildren={spaceChildren}
|
||||||
|
roomsToLeave={roomsToLeave}
|
||||||
|
setRoomsToLeave={setRoomsToLeave}
|
||||||
|
/> }
|
||||||
|
|
||||||
|
{ onlyAdminWarning && <div className="mx_LeaveSpaceDialog_section_warning">
|
||||||
|
{ onlyAdminWarning }
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
<DialogButtons
|
||||||
|
primaryButton={_t("Leave space")}
|
||||||
|
onPrimaryButtonClick={() => onFinished(true, roomsToLeave)}
|
||||||
|
hasCancel={true}
|
||||||
|
onCancel={onFinished}
|
||||||
|
/>
|
||||||
|
</BaseDialog>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LeaveSpaceDialog;
|
68
src/components/views/elements/JoinRuleDropdown.tsx
Normal file
68
src/components/views/elements/JoinRuleDropdown.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
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 from 'react';
|
||||||
|
import { JoinRule } from 'matrix-js-sdk/src/@types/partials';
|
||||||
|
|
||||||
|
import Dropdown from "./Dropdown";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
value: JoinRule;
|
||||||
|
label: string;
|
||||||
|
width?: number;
|
||||||
|
labelInvite: string;
|
||||||
|
labelPublic: string;
|
||||||
|
labelRestricted?: string; // if omitted then this option will be hidden, e.g if unsupported
|
||||||
|
onChange(value: JoinRule): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JoinRuleDropdown = ({
|
||||||
|
label,
|
||||||
|
labelInvite,
|
||||||
|
labelPublic,
|
||||||
|
labelRestricted,
|
||||||
|
value,
|
||||||
|
width = 448,
|
||||||
|
onChange,
|
||||||
|
}: IProps) => {
|
||||||
|
const options = [
|
||||||
|
<div key={JoinRule.Invite} className="mx_JoinRuleDropdown_invite">
|
||||||
|
{ labelInvite }
|
||||||
|
</div>,
|
||||||
|
<div key={JoinRule.Public} className="mx_JoinRuleDropdown_public">
|
||||||
|
{ labelPublic }
|
||||||
|
</div>,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (labelRestricted) {
|
||||||
|
options.unshift(<div key={JoinRule.Restricted} className="mx_JoinRuleDropdown_restricted">
|
||||||
|
{ labelRestricted }
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Dropdown
|
||||||
|
id="mx_JoinRuleDropdown"
|
||||||
|
className="mx_JoinRuleDropdown"
|
||||||
|
onOptionChange={onChange}
|
||||||
|
menuWidth={width}
|
||||||
|
value={value}
|
||||||
|
label={label}
|
||||||
|
>
|
||||||
|
{ options }
|
||||||
|
</Dropdown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default JoinRuleDropdown;
|
|
@ -192,7 +192,8 @@ class Pill extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserPillClicked = () => {
|
onUserPillClicked = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ViewUser,
|
action: Action.ViewUser,
|
||||||
member: this.state.member,
|
member: this.state.member,
|
||||||
|
|
|
@ -15,18 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { MatrixEvent } from 'matrix-js-sdk/src';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
|
||||||
@replaceableComponent("views.messages.ViewSourceEvent")
|
interface IProps {
|
||||||
export default class ViewSourceEvent extends React.PureComponent {
|
mxEvent: MatrixEvent;
|
||||||
static propTypes = {
|
}
|
||||||
/* the MatrixEvent to show */
|
|
||||||
mxEvent: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
expanded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.messages.ViewSourceEvent")
|
||||||
|
export default class ViewSourceEvent extends React.PureComponent<IProps, IState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ export default class ViewSourceEvent extends React.PureComponent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount(): void {
|
||||||
const { mxEvent } = this.props;
|
const { mxEvent } = this.props;
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
@ -46,15 +49,15 @@ export default class ViewSourceEvent extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggle = (ev) => {
|
private onToggle = (ev: React.MouseEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const { expanded } = this.state;
|
const { expanded } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: !expanded,
|
expanded: !expanded,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
public render(): React.ReactNode {
|
||||||
const { mxEvent } = this.props;
|
const { mxEvent } = this.props;
|
||||||
const { expanded } = this.state;
|
const { expanded } = this.state;
|
||||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext, useRef, useState } from "react";
|
import React, { ComponentProps, RefObject, SyntheticEvent, useContext, useRef, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { EventType, RoomType, RoomCreateTypeField } from "matrix-js-sdk/src/@types/event";
|
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import FocusLock from "react-focus-lock";
|
import FocusLock from "react-focus-lock";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
@ -24,18 +24,16 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
|
||||||
import createRoom from "../../../createRoom";
|
import createRoom from "../../../createRoom";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import { SpaceAvatar } from "./SpaceBasicSettings";
|
import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { BetaPill } from "../beta/BetaCard";
|
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|
||||||
import { Action } from "../../../dispatcher/actions";
|
|
||||||
import { UserTab } from "../dialogs/UserSettingsDialog";
|
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import withValidation from "../elements/Validation";
|
import withValidation from "../elements/Validation";
|
||||||
import { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
|
import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials";
|
||||||
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
|
||||||
import { ICreateRoomStateEvent } from "matrix-js-sdk/src/@types/requests";
|
|
||||||
import RoomAliasField from "../elements/RoomAliasField";
|
import RoomAliasField from "../elements/RoomAliasField";
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import GenericFeatureFeedbackDialog from "../dialogs/GenericFeatureFeedbackDialog";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -66,8 +64,111 @@ const nameToAlias = (name: string, domain: string): string => {
|
||||||
return `#${localpart}:${domain}`;
|
return `#${localpart}:${domain}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceCreateMenu = ({ onFinished }) => {
|
// XXX: Temporary for the Spaces release only
|
||||||
|
export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => {
|
||||||
|
if (!SdkConfig.get().bug_report_endpoint_url) return null;
|
||||||
|
|
||||||
|
return <div className="mx_SpaceFeedbackPrompt">
|
||||||
|
<span className="mx_SpaceFeedbackPrompt_text">{ _t("Spaces are a new feature.") }</span>
|
||||||
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
if (onClick) onClick();
|
||||||
|
Modal.createTrackedDialog("Spaces Feedback", "", GenericFeatureFeedbackDialog, {
|
||||||
|
title: _t("Spaces feedback"),
|
||||||
|
subheading: _t("Thank you for trying Spaces. " +
|
||||||
|
"Your feedback will help inform the next versions."),
|
||||||
|
rageshakeLabel: "spaces-feedback",
|
||||||
|
rageshakeData: Object.fromEntries([
|
||||||
|
"feature_spaces.all_rooms",
|
||||||
|
"feature_spaces.space_member_dms",
|
||||||
|
"feature_spaces.space_dm_badges",
|
||||||
|
].map(k => [k, SettingsStore.getValue(k)])),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Give feedback.") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BProps = Pick<ComponentProps<typeof SpaceBasicSettings>, "setAvatar" | "name" | "setName" | "topic" | "setTopic">;
|
||||||
|
interface ISpaceCreateFormProps extends BProps {
|
||||||
|
busy: boolean;
|
||||||
|
alias: string;
|
||||||
|
nameFieldRef: RefObject<Field>;
|
||||||
|
aliasFieldRef: RefObject<RoomAliasField>;
|
||||||
|
showAliasField?: boolean;
|
||||||
|
onSubmit(e: SyntheticEvent): void;
|
||||||
|
setAlias(alias: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
||||||
|
busy,
|
||||||
|
onSubmit,
|
||||||
|
setAvatar,
|
||||||
|
name,
|
||||||
|
setName,
|
||||||
|
nameFieldRef,
|
||||||
|
alias,
|
||||||
|
aliasFieldRef,
|
||||||
|
setAlias,
|
||||||
|
showAliasField,
|
||||||
|
topic,
|
||||||
|
setTopic,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const domain = cli.getDomain();
|
||||||
|
|
||||||
|
return <form className="mx_SpaceBasicSettings" onSubmit={onSubmit}>
|
||||||
|
<SpaceAvatar setAvatar={setAvatar} avatarDisabled={busy} />
|
||||||
|
|
||||||
|
<Field
|
||||||
|
name="spaceName"
|
||||||
|
label={_t("Name")}
|
||||||
|
autoFocus={true}
|
||||||
|
value={name}
|
||||||
|
onChange={ev => {
|
||||||
|
const newName = ev.target.value;
|
||||||
|
if (!alias || alias === nameToAlias(name, domain)) {
|
||||||
|
setAlias(nameToAlias(newName, domain));
|
||||||
|
}
|
||||||
|
setName(newName);
|
||||||
|
}}
|
||||||
|
ref={nameFieldRef}
|
||||||
|
onValidate={spaceNameValidator}
|
||||||
|
disabled={busy}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ showAliasField
|
||||||
|
? <RoomAliasField
|
||||||
|
ref={aliasFieldRef}
|
||||||
|
onChange={setAlias}
|
||||||
|
domain={domain}
|
||||||
|
value={alias}
|
||||||
|
placeholder={name ? nameToAlias(name, domain) : _t("e.g. my-space")}
|
||||||
|
label={_t("Address")}
|
||||||
|
disabled={busy}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
<Field
|
||||||
|
name="spaceTopic"
|
||||||
|
element="textarea"
|
||||||
|
label={_t("Description")}
|
||||||
|
value={topic}
|
||||||
|
onChange={ev => setTopic(ev.target.value)}
|
||||||
|
rows={3}
|
||||||
|
disabled={busy}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{ children }
|
||||||
|
</form>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
const [visibility, setVisibility] = useState<Visibility>(null);
|
const [visibility, setVisibility] = useState<Visibility>(null);
|
||||||
const [busy, setBusy] = useState<boolean>(false);
|
const [busy, setBusy] = useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -98,42 +199,26 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: ICreateRoomStateEvent[] = [
|
|
||||||
{
|
|
||||||
type: EventType.RoomHistoryVisibility,
|
|
||||||
content: {
|
|
||||||
"history_visibility": visibility === Visibility.Public ? "world_readable" : "invited",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (avatar) {
|
|
||||||
const url = await cli.uploadContent(avatar);
|
|
||||||
|
|
||||||
initialState.push({
|
|
||||||
type: EventType.RoomAvatar,
|
|
||||||
content: { url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createRoom({
|
await createRoom({
|
||||||
createOpts: {
|
createOpts: {
|
||||||
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
|
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
|
||||||
name,
|
name,
|
||||||
creation_content: {
|
|
||||||
[RoomCreateTypeField]: RoomType.Space,
|
|
||||||
},
|
|
||||||
initial_state: initialState,
|
|
||||||
power_level_content_override: {
|
power_level_content_override: {
|
||||||
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
// Only allow Admins to write to the timeline to prevent hidden sync spam
|
||||||
events_default: 100,
|
events_default: 100,
|
||||||
...Visibility.Public ? { invite: 0 } : {},
|
...visibility === Visibility.Public ? { invite: 0 } : {},
|
||||||
},
|
},
|
||||||
room_alias_name: visibility === Visibility.Public && alias
|
room_alias_name: visibility === Visibility.Public && alias
|
||||||
? alias.substr(1, alias.indexOf(":") - 1)
|
? alias.substr(1, alias.indexOf(":") - 1)
|
||||||
: undefined,
|
: undefined,
|
||||||
topic,
|
topic,
|
||||||
},
|
},
|
||||||
|
avatar,
|
||||||
|
roomType: RoomType.Space,
|
||||||
|
historyVisibility: visibility === Visibility.Public
|
||||||
|
? HistoryVisibility.WorldReadable
|
||||||
|
: HistoryVisibility.Invited,
|
||||||
spinner: false,
|
spinner: false,
|
||||||
encryption: false,
|
encryption: false,
|
||||||
andView: true,
|
andView: true,
|
||||||
|
@ -171,7 +256,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
<SpaceFeedbackPrompt onClick={onFinished} />
|
<SpaceFeedbackPrompt onClick={onFinished} />
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
} else {
|
} else {
|
||||||
const domain = cli.getDomain();
|
|
||||||
body = <React.Fragment>
|
body = <React.Fragment>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_SpaceCreateMenu_back"
|
className="mx_SpaceCreateMenu_back"
|
||||||
|
@ -192,50 +276,21 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form className="mx_SpaceBasicSettings" onSubmit={onSpaceCreateClick}>
|
<SpaceCreateForm
|
||||||
<SpaceAvatar setAvatar={setAvatar} avatarDisabled={busy} />
|
busy={busy}
|
||||||
|
onSubmit={onSpaceCreateClick}
|
||||||
<Field
|
setAvatar={setAvatar}
|
||||||
name="spaceName"
|
name={name}
|
||||||
label={_t("Name")}
|
setName={setName}
|
||||||
autoFocus={true}
|
nameFieldRef={spaceNameField}
|
||||||
value={name}
|
topic={topic}
|
||||||
onChange={ev => {
|
setTopic={setTopic}
|
||||||
const newName = ev.target.value;
|
alias={alias}
|
||||||
if (!alias || alias === nameToAlias(name, domain)) {
|
setAlias={setAlias}
|
||||||
setAlias(nameToAlias(newName, domain));
|
showAliasField={visibility === Visibility.Public}
|
||||||
}
|
aliasFieldRef={spaceAliasField}
|
||||||
setName(newName);
|
|
||||||
}}
|
|
||||||
ref={spaceNameField}
|
|
||||||
onValidate={spaceNameValidator}
|
|
||||||
disabled={busy}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ visibility === Visibility.Public
|
|
||||||
? <RoomAliasField
|
|
||||||
ref={spaceAliasField}
|
|
||||||
onChange={setAlias}
|
|
||||||
domain={domain}
|
|
||||||
value={alias}
|
|
||||||
placeholder={name ? nameToAlias(name, domain) : _t("e.g. my-space")}
|
|
||||||
label={_t("Address")}
|
|
||||||
disabled={busy}
|
|
||||||
/>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
<Field
|
|
||||||
name="spaceTopic"
|
|
||||||
element="textarea"
|
|
||||||
label={_t("Description")}
|
|
||||||
value={topic}
|
|
||||||
onChange={ev => setTopic(ev.target.value)}
|
|
||||||
rows={3}
|
|
||||||
disabled={busy}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
|
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
|
||||||
{ busy ? _t("Creating...") : _t("Create") }
|
{ busy ? _t("Creating...") : _t("Create") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -252,13 +307,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
managed={false}
|
managed={false}
|
||||||
>
|
>
|
||||||
<FocusLock returnFocus={true}>
|
<FocusLock returnFocus={true}>
|
||||||
<BetaPill onClick={() => {
|
|
||||||
onFinished();
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: Action.ViewUserSettings,
|
|
||||||
initialTabId: UserTab.Labs,
|
|
||||||
});
|
|
||||||
}} />
|
|
||||||
{ body }
|
{ body }
|
||||||
</FocusLock>
|
</FocusLock>
|
||||||
</ContextMenu>;
|
</ContextMenu>;
|
||||||
|
|
|
@ -21,12 +21,11 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
|
|
||||||
import SpaceBasicSettings from "./SpaceBasicSettings";
|
import SpaceBasicSettings from "./SpaceBasicSettings";
|
||||||
import { avatarUrlForRoom } from "../../../Avatar";
|
import { avatarUrlForRoom } from "../../../Avatar";
|
||||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||||
import { getTopic } from "../elements/RoomTopic";
|
import { getTopic } from "../elements/RoomTopic";
|
||||||
import { defaultDispatcher } from "../../../dispatcher/dispatcher";
|
import { leaveSpace } from "../../../utils/space";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
@ -96,8 +95,6 @@ const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProp
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
|
|
||||||
<SpaceFeedbackPrompt />
|
|
||||||
|
|
||||||
<div className="mx_SettingsTab_section">
|
<div className="mx_SettingsTab_section">
|
||||||
<SpaceBasicSettings
|
<SpaceBasicSettings
|
||||||
avatarUrl={avatarUrlForRoom(space, 80, 80, "crop")}
|
avatarUrl={avatarUrlForRoom(space, 80, 80, "crop")}
|
||||||
|
@ -128,10 +125,7 @@ const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProp
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="danger"
|
kind="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
defaultDispatcher.dispatch({
|
leaveSpace(space);
|
||||||
action: "leave_room",
|
|
||||||
room_id: space.roomId,
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ _t("Leave Space") }
|
{ _t("Leave Space") }
|
||||||
|
|
|
@ -31,9 +31,11 @@ import { _t } from "../../../languageHandler";
|
||||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||||
import { toRightOf } from "../../structures/ContextMenu";
|
import { toRightOf } from "../../structures/ContextMenu";
|
||||||
import {
|
import {
|
||||||
|
leaveSpace,
|
||||||
shouldShowSpaceSettings,
|
shouldShowSpaceSettings,
|
||||||
showAddExistingRooms,
|
showAddExistingRooms,
|
||||||
showCreateNewRoom,
|
showCreateNewRoom,
|
||||||
|
showCreateNewSubspace,
|
||||||
showSpaceInvite,
|
showSpaceInvite,
|
||||||
showSpaceSettings,
|
showSpaceSettings,
|
||||||
} from "../../../utils/space";
|
} from "../../../utils/space";
|
||||||
|
@ -48,6 +50,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
|
||||||
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
|
||||||
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
||||||
|
import { BetaPill } from "../beta/BetaCard";
|
||||||
|
|
||||||
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
|
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
|
||||||
space?: Room;
|
space?: Room;
|
||||||
|
@ -211,10 +214,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
defaultDispatcher.dispatch({
|
leaveSpace(this.props.space);
|
||||||
action: "leave_room",
|
|
||||||
room_id: this.props.space.roomId,
|
|
||||||
});
|
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,6 +234,14 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
this.setState({ contextMenuPosition: null }); // also close the menu
|
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onNewSubspaceClick = (ev: ButtonEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
showCreateNewSubspace(this.props.space);
|
||||||
|
this.setState({ contextMenuPosition: null }); // also close the menu
|
||||||
|
};
|
||||||
|
|
||||||
private onMembersClick = (ev: ButtonEvent) => {
|
private onMembersClick = (ev: ButtonEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
@ -318,6 +326,13 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
label={_t("Add existing room")}
|
label={_t("Add existing room")}
|
||||||
onClick={this.onAddExistingRoomClick}
|
onClick={this.onAddExistingRoomClick}
|
||||||
/>
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
iconClassName="mx_SpacePanel_iconPlus"
|
||||||
|
label={_t("Add space")}
|
||||||
|
onClick={this.onNewSubspaceClick}
|
||||||
|
>
|
||||||
|
<BetaPill />
|
||||||
|
</IconizedContextMenuOption>
|
||||||
</IconizedContextMenuOptionList>;
|
</IconizedContextMenuOptionList>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,7 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
private controlsHideTimer: number = null;
|
private controlsHideTimer: number = null;
|
||||||
private dialpadButton = createRef<HTMLDivElement>();
|
private dialpadButton = createRef<HTMLDivElement>();
|
||||||
private contextMenuButton = createRef<HTMLDivElement>();
|
private contextMenuButton = createRef<HTMLDivElement>();
|
||||||
|
private contextMenu = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -545,12 +546,42 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dialPad;
|
||||||
|
if (this.state.showDialpad) {
|
||||||
|
dialPad = <DialpadContextMenu
|
||||||
|
{...alwaysAboveRightOf(
|
||||||
|
this.dialpadButton.current.getBoundingClientRect(),
|
||||||
|
ChevronFace.None,
|
||||||
|
CONTEXT_MENU_VPADDING,
|
||||||
|
)}
|
||||||
|
mountAsChild={true}
|
||||||
|
onFinished={this.closeDialpad}
|
||||||
|
call={this.props.call}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contextMenu;
|
||||||
|
if (this.state.showMoreMenu) {
|
||||||
|
contextMenu = <CallContextMenu
|
||||||
|
{...alwaysAboveLeftOf(
|
||||||
|
this.contextMenuButton.current.getBoundingClientRect(),
|
||||||
|
ChevronFace.None,
|
||||||
|
CONTEXT_MENU_VPADDING,
|
||||||
|
)}
|
||||||
|
mountAsChild={true}
|
||||||
|
onFinished={this.closeContextMenu}
|
||||||
|
call={this.props.call}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={callControlsClasses}
|
className={callControlsClasses}
|
||||||
onMouseEnter={this.onCallControlsMouseEnter}
|
onMouseEnter={this.onCallControlsMouseEnter}
|
||||||
onMouseLeave={this.onCallControlsMouseLeave}
|
onMouseLeave={this.onCallControlsMouseLeave}
|
||||||
>
|
>
|
||||||
|
{ dialPad }
|
||||||
|
{ contextMenu }
|
||||||
{ dialpadButton }
|
{ dialpadButton }
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={micClasses}
|
className={micClasses}
|
||||||
|
@ -858,37 +889,9 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
myClassName = 'mx_CallView_pip';
|
myClassName = 'mx_CallView_pip';
|
||||||
}
|
}
|
||||||
|
|
||||||
let dialPad;
|
|
||||||
if (this.state.showDialpad) {
|
|
||||||
dialPad = <DialpadContextMenu
|
|
||||||
{...alwaysAboveRightOf(
|
|
||||||
this.dialpadButton.current.getBoundingClientRect(),
|
|
||||||
ChevronFace.None,
|
|
||||||
CONTEXT_MENU_VPADDING,
|
|
||||||
)}
|
|
||||||
onFinished={this.closeDialpad}
|
|
||||||
call={this.props.call}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextMenu;
|
|
||||||
if (this.state.showMoreMenu) {
|
|
||||||
contextMenu = <CallContextMenu
|
|
||||||
{...alwaysAboveLeftOf(
|
|
||||||
this.contextMenuButton.current.getBoundingClientRect(),
|
|
||||||
ChevronFace.None,
|
|
||||||
CONTEXT_MENU_VPADDING,
|
|
||||||
)}
|
|
||||||
onFinished={this.closeContextMenu}
|
|
||||||
call={this.props.call}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className={"mx_CallView " + myClassName}>
|
return <div className={"mx_CallView " + myClassName}>
|
||||||
{ header }
|
{ header }
|
||||||
{ contentView }
|
{ contentView }
|
||||||
{ dialPad }
|
|
||||||
{ contextMenu }
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,15 @@ limitations under the License.
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType, RoomCreateTypeField, RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
|
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
|
||||||
import { JoinRule, Preset, RestrictedAllowType, Visibility } from "matrix-js-sdk/src/@types/partials";
|
import {
|
||||||
|
HistoryVisibility,
|
||||||
|
JoinRule,
|
||||||
|
Preset,
|
||||||
|
RestrictedAllowType,
|
||||||
|
Visibility,
|
||||||
|
} from "matrix-js-sdk/src/@types/partials";
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
@ -52,6 +58,9 @@ export interface IOpts {
|
||||||
inlineErrors?: boolean;
|
inlineErrors?: boolean;
|
||||||
andView?: boolean;
|
andView?: boolean;
|
||||||
associatedWithCommunity?: string;
|
associatedWithCommunity?: string;
|
||||||
|
avatar?: File | string; // will upload if given file, else mxcUrl is needed
|
||||||
|
roomType?: RoomType | string;
|
||||||
|
historyVisibility?: HistoryVisibility;
|
||||||
parentSpace?: Room;
|
parentSpace?: Room;
|
||||||
joinRule?: JoinRule;
|
joinRule?: JoinRule;
|
||||||
}
|
}
|
||||||
|
@ -112,6 +121,13 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
||||||
createOpts.is_direct = true;
|
createOpts.is_direct = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.roomType) {
|
||||||
|
createOpts.creation_content = {
|
||||||
|
...createOpts.creation_content,
|
||||||
|
[RoomCreateTypeField]: opts.roomType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// By default, view the room after creating it
|
// By default, view the room after creating it
|
||||||
if (opts.andView === undefined) {
|
if (opts.andView === undefined) {
|
||||||
opts.andView = true;
|
opts.andView = true;
|
||||||
|
@ -144,12 +160,11 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
||||||
|
|
||||||
if (opts.parentSpace) {
|
if (opts.parentSpace) {
|
||||||
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
|
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
|
||||||
createOpts.initial_state.push({
|
if (!opts.historyVisibility) {
|
||||||
type: EventType.RoomHistoryVisibility,
|
opts.historyVisibility = createOpts.preset === Preset.PublicChat
|
||||||
content: {
|
? HistoryVisibility.WorldReadable
|
||||||
"history_visibility": createOpts.preset === Preset.PublicChat ? "world_readable" : "invited",
|
: HistoryVisibility.Invited;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.joinRule === JoinRule.Restricted) {
|
if (opts.joinRule === JoinRule.Restricted) {
|
||||||
if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) {
|
if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) {
|
||||||
|
@ -176,6 +191,27 @@ export default async function createRoom(opts: IOpts): Promise<string | null> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.avatar) {
|
||||||
|
let url = opts.avatar;
|
||||||
|
if (opts.avatar instanceof File) {
|
||||||
|
url = await client.uploadContent(opts.avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
createOpts.initial_state.push({
|
||||||
|
type: EventType.RoomAvatar,
|
||||||
|
content: { url },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.historyVisibility) {
|
||||||
|
createOpts.initial_state.push({
|
||||||
|
type: EventType.RoomHistoryVisibility,
|
||||||
|
content: {
|
||||||
|
"history_visibility": opts.historyVisibility,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let modal;
|
let modal;
|
||||||
if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
|
|
|
@ -193,4 +193,9 @@ export enum Action {
|
||||||
* Switches space. Should be used with SwitchSpacePayload.
|
* Switches space. Should be used with SwitchSpacePayload.
|
||||||
*/
|
*/
|
||||||
SwitchSpace = "switch_space",
|
SwitchSpace = "switch_space",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals to the visible space hierarchy that a change has occurred an that it should refresh.
|
||||||
|
*/
|
||||||
|
UpdateSpaceHierarchy = "update_space_hierarchy",
|
||||||
}
|
}
|
||||||
|
|
|
@ -1005,6 +1005,12 @@
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Description": "Description",
|
"Description": "Description",
|
||||||
"Please enter a name for the space": "Please enter a name for the space",
|
"Please enter a name for the space": "Please enter a name for the space",
|
||||||
|
"Spaces are a new feature.": "Spaces are a new feature.",
|
||||||
|
"Spaces feedback": "Spaces feedback",
|
||||||
|
"Thank you for trying Spaces. Your feedback will help inform the next versions.": "Thank you for trying Spaces. Your feedback will help inform the next versions.",
|
||||||
|
"Give feedback.": "Give feedback.",
|
||||||
|
"e.g. my-space": "e.g. my-space",
|
||||||
|
"Address": "Address",
|
||||||
"Create a space": "Create a space",
|
"Create a space": "Create a space",
|
||||||
"Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.",
|
"Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.",
|
||||||
"Public": "Public",
|
"Public": "Public",
|
||||||
|
@ -1017,8 +1023,6 @@
|
||||||
"Your private space": "Your private space",
|
"Your private space": "Your private space",
|
||||||
"Add some details to help people recognise it.": "Add some details to help people recognise it.",
|
"Add some details to help people recognise it.": "Add some details to help people recognise it.",
|
||||||
"You can change these anytime.": "You can change these anytime.",
|
"You can change these anytime.": "You can change these anytime.",
|
||||||
"e.g. my-space": "e.g. my-space",
|
|
||||||
"Address": "Address",
|
|
||||||
"Creating...": "Creating...",
|
"Creating...": "Creating...",
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"All rooms": "All rooms",
|
"All rooms": "All rooms",
|
||||||
|
@ -1056,6 +1060,7 @@
|
||||||
"Leave space": "Leave space",
|
"Leave space": "Leave space",
|
||||||
"Create new room": "Create new room",
|
"Create new room": "Create new room",
|
||||||
"Add existing room": "Add existing room",
|
"Add existing room": "Add existing room",
|
||||||
|
"Add space": "Add space",
|
||||||
"Members": "Members",
|
"Members": "Members",
|
||||||
"Manage & explore rooms": "Manage & explore rooms",
|
"Manage & explore rooms": "Manage & explore rooms",
|
||||||
"Explore rooms": "Explore rooms",
|
"Explore rooms": "Explore rooms",
|
||||||
|
@ -2110,17 +2115,20 @@
|
||||||
"Add a new server...": "Add a new server...",
|
"Add a new server...": "Add a new server...",
|
||||||
"%(networkName)s rooms": "%(networkName)s rooms",
|
"%(networkName)s rooms": "%(networkName)s rooms",
|
||||||
"Matrix rooms": "Matrix rooms",
|
"Matrix rooms": "Matrix rooms",
|
||||||
|
"Add existing space": "Add existing space",
|
||||||
|
"Want to add a new space instead?": "Want to add a new space instead?",
|
||||||
|
"Create a new space": "Create a new space",
|
||||||
|
"Search for spaces": "Search for spaces",
|
||||||
"Not all selected were added": "Not all selected were added",
|
"Not all selected were added": "Not all selected were added",
|
||||||
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
"Adding rooms... (%(progress)s out of %(count)s)|other": "Adding rooms... (%(progress)s out of %(count)s)",
|
||||||
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
"Adding rooms... (%(progress)s out of %(count)s)|one": "Adding room...",
|
||||||
"Filter your rooms and spaces": "Filter your rooms and spaces",
|
|
||||||
"Feeling experimental?": "Feeling experimental?",
|
|
||||||
"You can add existing spaces to a space.": "You can add existing spaces to a space.",
|
|
||||||
"Direct Messages": "Direct Messages",
|
"Direct Messages": "Direct Messages",
|
||||||
"Space selection": "Space selection",
|
"Space selection": "Space selection",
|
||||||
"Add existing rooms": "Add existing rooms",
|
"Add existing rooms": "Add existing rooms",
|
||||||
"Want to add a new room instead?": "Want to add a new room instead?",
|
"Want to add a new room instead?": "Want to add a new room instead?",
|
||||||
"Create a new room": "Create a new room",
|
"Create a new room": "Create a new room",
|
||||||
|
"Search for rooms": "Search for rooms",
|
||||||
|
"Adding spaces has moved.": "Adding spaces has moved.",
|
||||||
"Matrix ID": "Matrix ID",
|
"Matrix ID": "Matrix ID",
|
||||||
"Matrix Room ID": "Matrix Room ID",
|
"Matrix Room ID": "Matrix Room ID",
|
||||||
"email address": "email address",
|
"email address": "email address",
|
||||||
|
@ -2134,15 +2142,8 @@
|
||||||
"Invite anyway and never warn me again": "Invite anyway and never warn me again",
|
"Invite anyway and never warn me again": "Invite anyway and never warn me again",
|
||||||
"Invite anyway": "Invite anyway",
|
"Invite anyway": "Invite anyway",
|
||||||
"Close dialog": "Close dialog",
|
"Close dialog": "Close dialog",
|
||||||
"Beta feedback": "Beta feedback",
|
|
||||||
"Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
|
|
||||||
"Done": "Done",
|
|
||||||
"%(featureName)s beta feedback": "%(featureName)s beta feedback",
|
"%(featureName)s beta feedback": "%(featureName)s beta feedback",
|
||||||
"Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.",
|
|
||||||
"To leave the beta, visit your settings.": "To leave the beta, visit your settings.",
|
"To leave the beta, visit your settings.": "To leave the beta, visit your settings.",
|
||||||
"Feedback": "Feedback",
|
|
||||||
"You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions",
|
|
||||||
"Send feedback": "Send feedback",
|
|
||||||
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
|
"Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.",
|
||||||
"Preparing to send logs": "Preparing to send logs",
|
"Preparing to send logs": "Preparing to send logs",
|
||||||
"Logs sent": "Logs sent",
|
"Logs sent": "Logs sent",
|
||||||
|
@ -2208,13 +2209,22 @@
|
||||||
"Create a room in %(communityName)s": "Create a room in %(communityName)s",
|
"Create a room in %(communityName)s": "Create a room in %(communityName)s",
|
||||||
"Create a public room": "Create a public room",
|
"Create a public room": "Create a public room",
|
||||||
"Create a private room": "Create a private room",
|
"Create a private room": "Create a private room",
|
||||||
|
"Topic (optional)": "Topic (optional)",
|
||||||
|
"Room visibility": "Room visibility",
|
||||||
"Private room (invite only)": "Private room (invite only)",
|
"Private room (invite only)": "Private room (invite only)",
|
||||||
"Public room": "Public room",
|
"Public room": "Public room",
|
||||||
"Visible to space members": "Visible to space members",
|
"Visible to space members": "Visible to space members",
|
||||||
"Topic (optional)": "Topic (optional)",
|
|
||||||
"Room visibility": "Room visibility",
|
|
||||||
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
|
"Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.",
|
||||||
"Create Room": "Create Room",
|
"Create Room": "Create Room",
|
||||||
|
"Anyone in <SpaceName/> will be able to find and join.": "Anyone in <SpaceName/> will be able to find and join.",
|
||||||
|
"Anyone will be able to find and join this space, not just members of <SpaceName/>.": "Anyone will be able to find and join this space, not just members of <SpaceName/>.",
|
||||||
|
"Only people invited will be able to find and join this space.": "Only people invited will be able to find and join this space.",
|
||||||
|
"Add a space to a space you manage.": "Add a space to a space you manage.",
|
||||||
|
"Space visibility": "Space visibility",
|
||||||
|
"Private space (invite only)": "Private space (invite only)",
|
||||||
|
"Public space": "Public space",
|
||||||
|
"Want to add an existing space instead?": "Want to add an existing space instead?",
|
||||||
|
"Adding...": "Adding...",
|
||||||
"Sign out": "Sign out",
|
"Sign out": "Sign out",
|
||||||
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this",
|
"To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this": "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of %(brand)s to do this",
|
||||||
"You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.",
|
"You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.",
|
||||||
|
@ -2280,8 +2290,10 @@
|
||||||
"Comment": "Comment",
|
"Comment": "Comment",
|
||||||
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
|
"There are two ways you can provide feedback and help us improve %(brand)s.": "There are two ways you can provide feedback and help us improve %(brand)s.",
|
||||||
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
|
"PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.": "PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> to help us track down the problem.",
|
||||||
|
"Feedback": "Feedback",
|
||||||
"Report a bug": "Report a bug",
|
"Report a bug": "Report a bug",
|
||||||
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
|
"Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.": "Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. No match? <newIssueLink>Start a new one</newIssueLink>.",
|
||||||
|
"Send feedback": "Send feedback",
|
||||||
"You don't have permission to do this": "You don't have permission to do this",
|
"You don't have permission to do this": "You don't have permission to do this",
|
||||||
"Sending": "Sending",
|
"Sending": "Sending",
|
||||||
"Sent": "Sent",
|
"Sent": "Sent",
|
||||||
|
@ -2289,6 +2301,10 @@
|
||||||
"Forward message": "Forward message",
|
"Forward message": "Forward message",
|
||||||
"Message preview": "Message preview",
|
"Message preview": "Message preview",
|
||||||
"Search for rooms or people": "Search for rooms or people",
|
"Search for rooms or people": "Search for rooms or people",
|
||||||
|
"Thank you for your feedback, we really appreciate it.": "Thank you for your feedback, we really appreciate it.",
|
||||||
|
"Done": "Done",
|
||||||
|
"Your platform and username will be noted to help us use your feedback as much as we can.": "Your platform and username will be noted to help us use your feedback as much as we can.",
|
||||||
|
"You may contact me if you have any follow up questions": "You may contact me if you have any follow up questions",
|
||||||
"Confirm abort of host creation": "Confirm abort of host creation",
|
"Confirm abort of host creation": "Confirm abort of host creation",
|
||||||
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
|
"Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.",
|
||||||
"Abort": "Abort",
|
"Abort": "Abort",
|
||||||
|
@ -2360,6 +2376,15 @@
|
||||||
"Clear cache and resync": "Clear cache and resync",
|
"Clear cache and resync": "Clear cache and resync",
|
||||||
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
"%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
|
||||||
"Updating %(brand)s": "Updating %(brand)s",
|
"Updating %(brand)s": "Updating %(brand)s",
|
||||||
|
"Leave all rooms and spaces": "Leave all rooms and spaces",
|
||||||
|
"Don't leave any": "Don't leave any",
|
||||||
|
"Leave specific rooms and spaces": "Leave specific rooms and spaces",
|
||||||
|
"Search %(spaceName)s": "Search %(spaceName)s",
|
||||||
|
"You won't be able to rejoin unless you are re-invited.": "You won't be able to rejoin unless you are re-invited.",
|
||||||
|
"You're the only admin of this space. Leaving it will mean no one has control over it.": "You're the only admin of this space. Leaving it will mean no one has control over it.",
|
||||||
|
"You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.": "You're the only admin of some of the rooms or spaces you wish to leave. Leaving them will leave them without any admins.",
|
||||||
|
"Leave %(spaceName)s": "Leave %(spaceName)s",
|
||||||
|
"Are you sure you want to leave <spaceName/>?": "Are you sure you want to leave <spaceName/>?",
|
||||||
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
"Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
|
||||||
"Start using Key Backup": "Start using Key Backup",
|
"Start using Key Backup": "Start using Key Backup",
|
||||||
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
"I don't want my encrypted messages": "I don't want my encrypted messages",
|
||||||
|
@ -2800,8 +2825,6 @@
|
||||||
"Search names and descriptions": "Search names and descriptions",
|
"Search names and descriptions": "Search names and descriptions",
|
||||||
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
|
"If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.": "If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
|
||||||
"Create room": "Create room",
|
"Create room": "Create room",
|
||||||
"Spaces are a beta feature.": "Spaces are a beta feature.",
|
|
||||||
"Public space": "Public space",
|
|
||||||
"Private space": "Private space",
|
"Private space": "Private space",
|
||||||
"<inviter/> invites you": "<inviter/> invites you",
|
"<inviter/> invites you": "<inviter/> invites you",
|
||||||
"To view %(spaceName)s, turn on the <a>Spaces beta</a>": "To view %(spaceName)s, turn on the <a>Spaces beta</a>",
|
"To view %(spaceName)s, turn on the <a>Spaces beta</a>": "To view %(spaceName)s, turn on the <a>Spaces beta</a>",
|
||||||
|
@ -2816,6 +2839,7 @@
|
||||||
"Creating rooms...": "Creating rooms...",
|
"Creating rooms...": "Creating rooms...",
|
||||||
"What do you want to organise?": "What do you want to organise?",
|
"What do you want to organise?": "What do you want to organise?",
|
||||||
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
|
"Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.": "Pick rooms or conversations to add. This is just a space for you, no one will be informed. You can add more later.",
|
||||||
|
"Search for rooms or spaces": "Search for rooms or spaces",
|
||||||
"Share %(name)s": "Share %(name)s",
|
"Share %(name)s": "Share %(name)s",
|
||||||
"It's just you at the moment, it will be even better with others.": "It's just you at the moment, it will be even better with others.",
|
"It's just you at the moment, it will be even better with others.": "It's just you at the moment, it will be even better with others.",
|
||||||
"Go to my first room": "Go to my first room",
|
"Go to my first room": "Go to my first room",
|
||||||
|
@ -2827,7 +2851,7 @@
|
||||||
"Me and my teammates": "Me and my teammates",
|
"Me and my teammates": "Me and my teammates",
|
||||||
"A private space for you and your teammates": "A private space for you and your teammates",
|
"A private space for you and your teammates": "A private space for you and your teammates",
|
||||||
"Teammates might not be able to view or join any private rooms you make.": "Teammates might not be able to view or join any private rooms you make.",
|
"Teammates might not be able to view or join any private rooms you make.": "Teammates might not be able to view or join any private rooms you make.",
|
||||||
"We're working on this as part of the beta, but just want to let you know.": "We're working on this as part of the beta, but just want to let you know.",
|
"We're working on this, but just want to let you know.": "We're working on this, but just want to let you know.",
|
||||||
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
|
"Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s",
|
||||||
"Inviting...": "Inviting...",
|
"Inviting...": "Inviting...",
|
||||||
"Invite your teammates": "Invite your teammates",
|
"Invite your teammates": "Invite your teammates",
|
||||||
|
|
|
@ -124,6 +124,7 @@ export interface ISetting {
|
||||||
// not use this for new settings.
|
// not use this for new settings.
|
||||||
invertedSettingName?: string;
|
invertedSettingName?: string;
|
||||||
|
|
||||||
|
// XXX: Keep this around for re-use in future Betas
|
||||||
betaInfo?: {
|
betaInfo?: {
|
||||||
title: string; // _td
|
title: string; // _td
|
||||||
caption: string; // _td
|
caption: string; // _td
|
||||||
|
|
|
@ -329,7 +329,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}, roomId);
|
}, roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getChildren(spaceId: string): Room[] {
|
public getChildren(spaceId: string): Room[] {
|
||||||
const room = this.matrixClient?.getRoom(spaceId);
|
const room = this.matrixClient?.getRoom(spaceId);
|
||||||
const childEvents = room?.currentState.getStateEvents(EventType.SpaceChild).filter(ev => ev.getContent()?.via);
|
const childEvents = room?.currentState.getStateEvents(EventType.SpaceChild).filter(ev => ev.getContent()?.via);
|
||||||
return sortBy(childEvents, ev => {
|
return sortBy(childEvents, ev => {
|
||||||
|
|
|
@ -141,21 +141,3 @@ export function objectKeyChanges<O extends {}>(a: O, b: O): (keyof O)[] {
|
||||||
export function objectClone<O extends {}>(obj: O): O {
|
export function objectClone<O extends {}>(obj: O): O {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a series of entries to an object.
|
|
||||||
* @param entries The entries to convert.
|
|
||||||
* @returns The converted object.
|
|
||||||
*/
|
|
||||||
// NOTE: Deprecated once we have Object.fromEntries() support.
|
|
||||||
// @ts-ignore - return type is complaining about non-string keys, but we know better
|
|
||||||
export function objectFromEntries<K, V>(entries: Iterable<[K, V]>): {[k: K]: V} {
|
|
||||||
const obj: {
|
|
||||||
// @ts-ignore - same as return type
|
|
||||||
[k: K]: V;} = {};
|
|
||||||
for (const e of entries) {
|
|
||||||
// @ts-ignore - same as return type
|
|
||||||
obj[e[0]] = e[1];
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
|
@ -28,6 +28,15 @@ import { _t } from "../languageHandler";
|
||||||
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
|
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
|
||||||
import InfoDialog from "../components/views/dialogs/InfoDialog";
|
import InfoDialog from "../components/views/dialogs/InfoDialog";
|
||||||
import { showRoomInviteDialog } from "../RoomInvite";
|
import { showRoomInviteDialog } from "../RoomInvite";
|
||||||
|
import CreateSubspaceDialog from "../components/views/dialogs/CreateSubspaceDialog";
|
||||||
|
import AddExistingSubspaceDialog from "../components/views/dialogs/AddExistingSubspaceDialog";
|
||||||
|
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
|
import RoomViewStore from "../stores/RoomViewStore";
|
||||||
|
import { Action } from "../dispatcher/actions";
|
||||||
|
import { leaveRoomBehaviour } from "./membership";
|
||||||
|
import Spinner from "../components/views/elements/Spinner";
|
||||||
|
import dis from "../dispatcher/dispatcher";
|
||||||
|
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
|
||||||
|
|
||||||
export const shouldShowSpaceSettings = (space: Room) => {
|
export const shouldShowSpaceSettings = (space: Room) => {
|
||||||
const userId = space.client.getUserId();
|
const userId = space.client.getUserId();
|
||||||
|
@ -54,21 +63,26 @@ export const showSpaceSettings = (space: Room) => {
|
||||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showAddExistingRooms = async (space: Room) => {
|
export const showAddExistingRooms = (space: Room): void => {
|
||||||
return Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
"Space Landing",
|
"Space Landing",
|
||||||
"Add Existing",
|
"Add Existing",
|
||||||
AddExistingToSpaceDialog,
|
AddExistingToSpaceDialog,
|
||||||
{
|
{
|
||||||
matrixClient: space.client,
|
onCreateRoomClick: () => showCreateNewRoom(space),
|
||||||
onCreateRoomClick: showCreateNewRoom,
|
onAddSubspaceClick: () => showAddExistingSubspace(space),
|
||||||
space,
|
space,
|
||||||
|
onFinished: (added: boolean) => {
|
||||||
|
if (added && RoomViewStore.getRoomId() === space.roomId) {
|
||||||
|
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"mx_AddExistingToSpaceDialog_wrapper",
|
"mx_AddExistingToSpaceDialog_wrapper",
|
||||||
).finished;
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showCreateNewRoom = async (space: Room) => {
|
export const showCreateNewRoom = async (space: Room): Promise<boolean> => {
|
||||||
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
|
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
|
||||||
"Space Landing",
|
"Space Landing",
|
||||||
"Create Room",
|
"Create Room",
|
||||||
|
@ -85,7 +99,7 @@ export const showCreateNewRoom = async (space: Room) => {
|
||||||
return shouldCreate;
|
return shouldCreate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showSpaceInvite = (space: Room, initialText = "") => {
|
export const showSpaceInvite = (space: Room, initialText = ""): void => {
|
||||||
if (space.getJoinRule() === "public") {
|
if (space.getJoinRule() === "public") {
|
||||||
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
||||||
title: _t("Invite to %(spaceName)s", { spaceName: space.name }),
|
title: _t("Invite to %(spaceName)s", { spaceName: space.name }),
|
||||||
|
@ -102,3 +116,60 @@ export const showSpaceInvite = (space: Room, initialText = "") => {
|
||||||
showRoomInviteDialog(space.roomId, initialText);
|
showRoomInviteDialog(space.roomId, initialText);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const showAddExistingSubspace = (space: Room): void => {
|
||||||
|
Modal.createTrackedDialog(
|
||||||
|
"Space Landing",
|
||||||
|
"Create Subspace",
|
||||||
|
AddExistingSubspaceDialog,
|
||||||
|
{
|
||||||
|
space,
|
||||||
|
onCreateSubspaceClick: () => showCreateNewSubspace(space),
|
||||||
|
onFinished: (added: boolean) => {
|
||||||
|
if (added && RoomViewStore.getRoomId() === space.roomId) {
|
||||||
|
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mx_AddExistingToSpaceDialog_wrapper",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const showCreateNewSubspace = (space: Room): void => {
|
||||||
|
Modal.createTrackedDialog(
|
||||||
|
"Space Landing",
|
||||||
|
"Create Subspace",
|
||||||
|
CreateSubspaceDialog,
|
||||||
|
{
|
||||||
|
space,
|
||||||
|
onAddExistingSpaceClick: () => showAddExistingSubspace(space),
|
||||||
|
onFinished: (added: boolean) => {
|
||||||
|
if (added && RoomViewStore.getRoomId() === space.roomId) {
|
||||||
|
defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"mx_CreateSubspaceDialog_wrapper",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const leaveSpace = (space: Room) => {
|
||||||
|
Modal.createTrackedDialog("Leave Space", "", LeaveSpaceDialog, {
|
||||||
|
space,
|
||||||
|
onFinished: async (leave: boolean, rooms: Room[]) => {
|
||||||
|
if (!leave) return;
|
||||||
|
const modal = Modal.createDialog(Spinner, null, "mx_Dialog_spinner");
|
||||||
|
try {
|
||||||
|
await Promise.all(rooms.map(r => leaveRoomBehaviour(r.roomId)));
|
||||||
|
await leaveRoomBehaviour(space.roomId);
|
||||||
|
} finally {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
dis.dispatch({
|
||||||
|
action: "after_leave_room",
|
||||||
|
room_id: space.roomId,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}, "mx_LeaveSpaceDialog_wrapper");
|
||||||
|
};
|
||||||
|
|
|
@ -29,7 +29,6 @@ import {
|
||||||
ArrayUtil,
|
ArrayUtil,
|
||||||
GroupedArray,
|
GroupedArray,
|
||||||
} from "../../src/utils/arrays";
|
} from "../../src/utils/arrays";
|
||||||
import { objectFromEntries } from "../../src/utils/objects";
|
|
||||||
|
|
||||||
function expectSample(i: number, input: number[], expected: number[], smooth = false) {
|
function expectSample(i: number, input: number[], expected: number[], smooth = false) {
|
||||||
console.log(`Resample case index: ${i}`); // for debugging test failures
|
console.log(`Resample case index: ${i}`); // for debugging test failures
|
||||||
|
@ -336,7 +335,7 @@ describe('arrays', () => {
|
||||||
expect(result).toBeDefined();
|
expect(result).toBeDefined();
|
||||||
expect(result.value).toBeDefined();
|
expect(result.value).toBeDefined();
|
||||||
|
|
||||||
const asObject = objectFromEntries(result.value.entries());
|
const asObject = Object.fromEntries(result.value.entries());
|
||||||
expect(asObject).toMatchObject(output);
|
expect(asObject).toMatchObject(output);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,6 @@ import {
|
||||||
objectClone,
|
objectClone,
|
||||||
objectDiff,
|
objectDiff,
|
||||||
objectExcluding,
|
objectExcluding,
|
||||||
objectFromEntries,
|
|
||||||
objectHasDiff,
|
objectHasDiff,
|
||||||
objectKeyChanges,
|
objectKeyChanges,
|
||||||
objectShallowClone,
|
objectShallowClone,
|
||||||
|
@ -242,21 +241,4 @@ describe('objects', () => {
|
||||||
expect(result.test.third).not.toBe(a.test.third);
|
expect(result.test.third).not.toBe(a.test.third);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('objectFromEntries', () => {
|
|
||||||
it('should create an object from an array of entries', () => {
|
|
||||||
const output = { a: 1, b: 2, c: 3 };
|
|
||||||
const result = objectFromEntries(Object.entries(output));
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result).toMatchObject(output);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should maintain pointers in values', () => {
|
|
||||||
const output = { a: {}, b: 2, c: 3 };
|
|
||||||
const result = objectFromEntries(Object.entries(output));
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result).toMatchObject(output);
|
|
||||||
expect(result['a']).toBe(output.a);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue