Merge pull request #5792 from matrix-org/t3chguy/spaces4.12
Fixing spaces papercuts
This commit is contained in:
commit
83612dd4ad
24 changed files with 502 additions and 349 deletions
|
@ -117,6 +117,7 @@
|
||||||
@import "./views/elements/_EditableItemList.scss";
|
@import "./views/elements/_EditableItemList.scss";
|
||||||
@import "./views/elements/_ErrorBoundary.scss";
|
@import "./views/elements/_ErrorBoundary.scss";
|
||||||
@import "./views/elements/_EventListSummary.scss";
|
@import "./views/elements/_EventListSummary.scss";
|
||||||
|
@import "./views/elements/_FacePile.scss";
|
||||||
@import "./views/elements/_Field.scss";
|
@import "./views/elements/_Field.scss";
|
||||||
@import "./views/elements/_FormButton.scss";
|
@import "./views/elements/_FormButton.scss";
|
||||||
@import "./views/elements/_ImageView.scss";
|
@import "./views/elements/_ImageView.scss";
|
||||||
|
|
|
@ -22,7 +22,7 @@ limitations under the License.
|
||||||
// keep border thickness consistent to prevent movement
|
// keep border thickness consistent to prevent movement
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
padding: 2px;
|
padding: 1px;
|
||||||
|
|
||||||
// Create a flexbox for the icons (easier to manage)
|
// Create a flexbox for the icons (easier to manage)
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -330,10 +330,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_iconHome::before {
|
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/home.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpacePanel_iconMembers::before {
|
.mx_SpacePanel_iconMembers::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,7 +182,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile {
|
.mx_SpaceRoomDirectory_roomTile {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 6px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -190,6 +190,7 @@ limitations under the License.
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20px auto max-content;
|
grid-template-columns: 20px auto max-content;
|
||||||
grid-column-gap: 8px;
|
grid-column-gap: 8px;
|
||||||
|
grid-row-gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
.mx_BaseAvatar {
|
||||||
|
@ -213,16 +214,28 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_InfoTooltip_icon {
|
.mx_InfoTooltip_icon {
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: text-top;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_info {
|
.mx_SpaceRoomDirectory_roomTile_info {
|
||||||
font-size: $font-12px;
|
font-size: $font-14px;
|
||||||
line-height: $font-15px;
|
line-height: $font-18px;
|
||||||
color: $tertiary-fg-color;
|
color: $secondary-fg-color;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
grid-column: 1/3;
|
grid-column: 1/3;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_actions {
|
.mx_SpaceRoomDirectory_actions {
|
||||||
|
@ -232,9 +245,9 @@ limitations under the License.
|
||||||
grid-row: 1/3;
|
grid-row: 1/3;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
padding: 6px 18px;
|
padding: 8px 18px;
|
||||||
|
display: inline-block;
|
||||||
display: none;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Checkbox {
|
.mx_Checkbox {
|
||||||
|
@ -248,7 +261,7 @@ limitations under the License.
|
||||||
background-color: $groupFilterPanel-bg-color;
|
background-color: $groupFilterPanel-bg-color;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
display: inline-block;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
width: 432px;
|
width: 432px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid $space-button-outline-color;
|
border: 1px solid $input-border-color;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
max-width: 480px;
|
max-width: 480px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
||||||
border: 1px solid $input-border-color;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_inviter {
|
.mx_SpaceRoomView_preview_inviter {
|
||||||
|
@ -154,53 +153,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
margin: 20px 0 !important; // override default margin from above
|
margin: 20px 0 !important; // override default margin from above
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_info {
|
|
||||||
color: $tertiary-fg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
margin: 20px 0;
|
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_info_public,
|
|
||||||
.mx_SpaceRoomView_preview_info_private {
|
|
||||||
padding-left: 20px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
top: 0;
|
|
||||||
left: -2px;
|
|
||||||
mask-position: center;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
background-color: $tertiary-fg-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_info_public::before {
|
|
||||||
mask-size: 12px;
|
|
||||||
mask-image: url("$(res)/img/globe.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_info_private::before {
|
|
||||||
mask-size: 14px;
|
|
||||||
mask-image: url("$(res)/img/element-icons/lock.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_link {
|
|
||||||
color: inherit;
|
|
||||||
position: relative;
|
|
||||||
padding-left: 16px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "·"; // visual separator
|
|
||||||
position: absolute;
|
|
||||||
left: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomView_preview_topic {
|
.mx_SpaceRoomView_preview_topic {
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
line-height: $font-22px;
|
line-height: $font-22px;
|
||||||
|
@ -254,36 +206,90 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_memberCount {
|
.mx_SpaceRoomView_landing_info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_info {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FacePile {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 12px;
|
||||||
|
|
||||||
|
.mx_FacePile_faces {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> span:hover {
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
filter: brightness(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> span:first-child {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 24px;
|
|
||||||
padding: 0 0 0 28px;
|
.mx_BaseAvatar {
|
||||||
line-height: $font-24px;
|
filter: brightness(0.8);
|
||||||
vertical-align: text-bottom;
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
z-index: 1;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
background: #ffffff; // white icon fill
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: 24px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_landing_inviteButton {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 40px;
|
||||||
|
height: min-content;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
content: '';
|
content: "";
|
||||||
width: 24px;
|
left: 8px;
|
||||||
height: 24px;
|
height: 16px;
|
||||||
top: 0;
|
width: 16px;
|
||||||
left: 0;
|
background: #ffffff; // white icon fill
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
|
mask-size: 16px;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: contain;
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
background-color: $accent-color;
|
|
||||||
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_topic {
|
.mx_SpaceRoomView_landing_topic {
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background-color: $groupFilterPanel-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_landing_adminButtons {
|
.mx_SpaceRoomView_landing_adminButtons {
|
||||||
margin-top: 32px;
|
margin-top: 24px;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -292,9 +298,9 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 72px 16px 0;
|
padding: 72px 16px 0;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid $space-button-outline-color;
|
border: 1px solid $input-border-color;
|
||||||
margin-right: 28px;
|
margin-right: 28px;
|
||||||
margin-bottom: 28px;
|
margin-bottom: 20px;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
|
@ -324,16 +330,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
background: #ffffff; // white icon fill
|
background: #ffffff; // white icon fill
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_SpaceRoomView_landing_inviteButton {
|
|
||||||
&::before {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpaceRoomView_landing_addButton {
|
&.mx_SpaceRoomView_landing_addButton {
|
||||||
&::before {
|
&::before {
|
||||||
background-color: #ac3ba8;
|
background-color: #ac3ba8;
|
||||||
|
@ -366,12 +362,8 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_list {
|
.mx_SearchBox {
|
||||||
max-width: 600px;
|
margin: 0 0 20px;
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_actions {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,3 +416,50 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_info {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_info_public,
|
||||||
|
.mx_SpaceRoomView_info_private {
|
||||||
|
padding-left: 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 0;
|
||||||
|
left: -2px;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_info_public::before {
|
||||||
|
mask-size: 12px;
|
||||||
|
mask-image: url("$(res)/img/globe.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomView_info_private::before {
|
||||||
|
mask-size: 14px;
|
||||||
|
mask-image: url("$(res)/img/element-icons/lock.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
color: inherit;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "·"; // visual separator
|
||||||
|
position: absolute;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
42
res/css/views/elements/_FacePile.scss
Normal file
42
res/css/views/elements/_FacePile.scss
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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_FacePile {
|
||||||
|
.mx_FacePile_faces {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
> span + span {
|
||||||
|
margin-right: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
border: 1px solid $primary-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar_initial {
|
||||||
|
margin: 1px; // to offset the border on the image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin-left: 12px;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,7 @@ limitations under the License.
|
||||||
.mx_RoomList_explorePrompt {
|
.mx_RoomList_explorePrompt {
|
||||||
margin: 4px 12px 4px;
|
margin: 4px 12px 4px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid $tertiary-fg-color;
|
border-top: 1px solid $input-border-color;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
|
|
||||||
div:first-child {
|
div:first-child {
|
||||||
|
|
|
@ -123,7 +123,6 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: rgba(141, 151, 165, 0.2);
|
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
|
|
@ -120,7 +120,6 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: rgba(141, 151, 165, 0.2);
|
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
|
|
|
@ -187,7 +187,6 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: #E3E8F0;
|
|
||||||
|
|
||||||
$voice-record-stop-border-color: #E3E8F0;
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
$voice-record-stop-symbol-color: $warning-color;
|
$voice-record-stop-symbol-color: $warning-color;
|
||||||
|
|
|
@ -178,7 +178,6 @@ $roomsublist-divider-color: $primary-fg-color;
|
||||||
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
|
||||||
|
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: #E3E8F0;
|
|
||||||
|
|
||||||
$voice-record-stop-border-color: #E3E8F0;
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
$voice-record-stop-symbol-color: $warning-color;
|
$voice-record-stop-symbol-color: $warning-color;
|
||||||
|
|
|
@ -80,10 +80,10 @@ import DialPadModal from "../views/voip/DialPadModal";
|
||||||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||||
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
import SpaceRoomDirectory from "./SpaceRoomDirectory";
|
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import {RoomUpdateCause} from "../../stores/room-list/models";
|
import {RoomUpdateCause} from "../../stores/room-list/models";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -690,10 +690,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
case Action.ViewRoomDirectory: {
|
case Action.ViewRoomDirectory: {
|
||||||
if (SpaceStore.instance.activeSpace) {
|
if (SpaceStore.instance.activeSpace) {
|
||||||
Modal.createTrackedDialog("Space room directory", "", SpaceRoomDirectory, {
|
defaultDispatcher.dispatch({
|
||||||
space: SpaceStore.instance.activeSpace,
|
action: "view_room",
|
||||||
initialText: payload.initialText,
|
room_id: SpaceStore.instance.activeSpace.roomId,
|
||||||
}, "mx_SpaceRoomDirectory_dialogWrapper", false, true);
|
});
|
||||||
} else {
|
} else {
|
||||||
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
||||||
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,7 @@ import { Action } from "../../dispatcher/actions";
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -53,6 +54,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
|
// clear filter when changing spaces, in future we may wish to maintain a filter per-space
|
||||||
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
public componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>): void {
|
||||||
|
@ -72,6 +75,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
|
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.clearInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
private onAction = (payload: ActionPayload) => {
|
||||||
|
|
|
@ -40,10 +40,11 @@ import InfoTooltip from "../views/elements/InfoTooltip";
|
||||||
import TextWithTooltip from "../views/elements/TextWithTooltip";
|
import TextWithTooltip from "../views/elements/TextWithTooltip";
|
||||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
|
|
||||||
interface IProps {
|
interface IHierarchyProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
onFinished(): void;
|
refreshToken?: any;
|
||||||
|
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -111,7 +112,7 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
let button;
|
let button;
|
||||||
if (myMembership === "join") {
|
if (myMembership === "join") {
|
||||||
button = <AccessibleButton onClick={onPreviewClick} kind="primary_outline">
|
button = <AccessibleButton onClick={onPreviewClick} kind="primary_outline">
|
||||||
{ _t("Open") }
|
{ _t("View") }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
} else if (onJoinClick) {
|
} else if (onJoinClick) {
|
||||||
button = <AccessibleButton onClick={onJoinClick} kind="primary">
|
button = <AccessibleButton onClick={onJoinClick} kind="primary">
|
||||||
|
@ -251,7 +252,7 @@ export const HierarchyLevel = ({
|
||||||
}: IHierarchyLevelProps) => {
|
}: IHierarchyLevelProps) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const space = cli.getRoom(spaceId);
|
const space = cli.getRoom(spaceId);
|
||||||
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
|
||||||
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
|
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
|
||||||
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
||||||
|
@ -344,22 +345,20 @@ export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: a
|
||||||
}, [space, refreshToken], []);
|
}, [space, refreshToken], []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
|
||||||
|
space,
|
||||||
|
initialText = "",
|
||||||
|
showRoom,
|
||||||
|
refreshToken,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
const [query, setQuery] = useState(initialText);
|
const [query, setQuery] = useState(initialText);
|
||||||
|
|
||||||
const onCreateRoomClick = () => {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_create_room',
|
|
||||||
public: true,
|
|
||||||
});
|
|
||||||
onFinished();
|
|
||||||
};
|
|
||||||
|
|
||||||
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 [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space);
|
const [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space, refreshToken);
|
||||||
|
|
||||||
const roomsMap = useMemo(() => {
|
const roomsMap = useMemo(() => {
|
||||||
if (!rooms) return null;
|
if (!rooms) return null;
|
||||||
|
@ -394,21 +393,6 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
return roomsMap;
|
return roomsMap;
|
||||||
}, [rooms, childParentMap, query]);
|
}, [rooms, childParentMap, query]);
|
||||||
|
|
||||||
const title = <React.Fragment>
|
|
||||||
<RoomAvatar room={space} height={32} width={32} />
|
|
||||||
<div>
|
|
||||||
<h1>{ _t("Explore rooms") }</h1>
|
|
||||||
<div><RoomName room={space} /></div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>;
|
|
||||||
|
|
||||||
const explanation =
|
|
||||||
_t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.", null,
|
|
||||||
{a: sub => {
|
|
||||||
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
|
||||||
}},
|
|
||||||
);
|
|
||||||
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [removing, setRemoving] = useState(false);
|
const [removing, setRemoving] = useState(false);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
@ -503,6 +487,8 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
|
|
||||||
let results;
|
let results;
|
||||||
if (roomsMap.size) {
|
if (roomsMap.size) {
|
||||||
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
|
||||||
|
|
||||||
results = <>
|
results = <>
|
||||||
<HierarchyLevel
|
<HierarchyLevel
|
||||||
spaceId={space.roomId}
|
spaceId={space.roomId}
|
||||||
|
@ -510,7 +496,7 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
relations={parentChildMap}
|
relations={parentChildMap}
|
||||||
parents={new Set()}
|
parents={new Set()}
|
||||||
selectedMap={selected}
|
selectedMap={selected}
|
||||||
onToggleClick={(parentId, childId) => {
|
onToggleClick={hasPermissions ? (parentId, childId) => {
|
||||||
setError("");
|
setError("");
|
||||||
if (!selected.has(parentId)) {
|
if (!selected.has(parentId)) {
|
||||||
setSelected(new Map(selected.set(parentId, new Set([childId]))));
|
setSelected(new Map(selected.set(parentId, new Set([childId]))));
|
||||||
|
@ -525,13 +511,12 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
|
|
||||||
parentSet.delete(childId);
|
parentSet.delete(childId);
|
||||||
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
|
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
|
||||||
}}
|
} : undefined}
|
||||||
onViewRoomClick={(roomId, autoJoin) => {
|
onViewRoomClick={(roomId, autoJoin) => {
|
||||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
||||||
onFinished();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<hr />
|
{ children && <hr /> }
|
||||||
</>;
|
</>;
|
||||||
} else {
|
} else {
|
||||||
results = <div className="mx_SpaceRoomDirectory_noResults">
|
results = <div className="mx_SpaceRoomDirectory_noResults">
|
||||||
|
@ -550,25 +535,17 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
</div> }
|
</div> }
|
||||||
<AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
<AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
||||||
{ results }
|
{ results }
|
||||||
<AccessibleButton
|
{ children }
|
||||||
onClick={onCreateRoomClick}
|
|
||||||
kind="primary"
|
|
||||||
className="mx_SpaceRoomDirectory_createRoom"
|
|
||||||
>
|
|
||||||
{ _t("Create room") }
|
|
||||||
</AccessibleButton>
|
|
||||||
</AutoHideScrollbar>
|
</AutoHideScrollbar>
|
||||||
</>;
|
</>;
|
||||||
} else {
|
} else if (!rooms) {
|
||||||
content = <Spinner />;
|
content = <Spinner />;
|
||||||
|
} else {
|
||||||
|
content = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO loading state/error state
|
// TODO loading state/error state
|
||||||
return (
|
return <>
|
||||||
<BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
{ explanation }
|
|
||||||
|
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={ _t("Search names and description") }
|
placeholder={ _t("Search names and description") }
|
||||||
|
@ -578,6 +555,58 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ content }
|
{ content }
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
space: Room;
|
||||||
|
initialText?: string;
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }) => {
|
||||||
|
const onCreateRoomClick = () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_create_room',
|
||||||
|
public: true,
|
||||||
|
});
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = <React.Fragment>
|
||||||
|
<RoomAvatar room={space} height={32} width={32} />
|
||||||
|
<div>
|
||||||
|
<h1>{ _t("Explore rooms") }</h1>
|
||||||
|
<div><RoomName room={space} /></div>
|
||||||
|
</div>
|
||||||
|
</React.Fragment>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className="mx_SpaceRoomDirectory" hasCancel={true} onFinished={onFinished} title={title}>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
{ _t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
|
||||||
|
null,
|
||||||
|
{a: sub => {
|
||||||
|
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
||||||
|
}},
|
||||||
|
) }
|
||||||
|
|
||||||
|
<SpaceHierarchy
|
||||||
|
space={space}
|
||||||
|
showRoom={(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => {
|
||||||
|
showRoom(room, viaServers, autoJoin);
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
initialText={initialText}
|
||||||
|
>
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={onCreateRoomClick}
|
||||||
|
kind="primary"
|
||||||
|
className="mx_SpaceRoomDirectory_createRoom"
|
||||||
|
>
|
||||||
|
{ _t("Create room") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</SpaceHierarchy>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {RefObject, useContext, useMemo, useRef, useState} from "react";
|
import React, {RefObject, useContext, useRef, useState} from "react";
|
||||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import {EventSubscription} from "fbemitter";
|
import {EventSubscription} from "fbemitter";
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanel
|
||||||
import {useStateArray} from "../../hooks/useStateArray";
|
import {useStateArray} from "../../hooks/useStateArray";
|
||||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||||
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||||
import {HierarchyLevel, ISpaceSummaryRoom, showRoom, useSpaceSummary} from "./SpaceRoomDirectory";
|
import {showRoom, SpaceHierarchy} from "./SpaceRoomDirectory";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
|
||||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
|
import FacePile from "../views/elements/FacePile";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -92,6 +92,41 @@ const useMyRoomMembership = (room: Room) => {
|
||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SpaceInfo = ({ space }) => {
|
||||||
|
const joinRule = space.getJoinRule();
|
||||||
|
|
||||||
|
let visibilitySection;
|
||||||
|
if (joinRule === "public") {
|
||||||
|
visibilitySection = <span className="mx_SpaceRoomView_info_public">
|
||||||
|
{ _t("Public space") }
|
||||||
|
</span>;
|
||||||
|
} else {
|
||||||
|
visibilitySection = <span className="mx_SpaceRoomView_info_private">
|
||||||
|
{ _t("Private space") }
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="mx_SpaceRoomView_info">
|
||||||
|
{ visibilitySection }
|
||||||
|
{ joinRule === "public" && <RoomMemberCount room={space}>
|
||||||
|
{(count) => count > 0 ? (
|
||||||
|
<AccessibleButton
|
||||||
|
kind="link"
|
||||||
|
onClick={() => {
|
||||||
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
|
action: Action.SetRightPanelPhase,
|
||||||
|
phase: RightPanelPhases.RoomMemberList,
|
||||||
|
refireParams: { space },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("%(count)s members", { count }) }
|
||||||
|
</AccessibleButton>
|
||||||
|
) : null}
|
||||||
|
</RoomMemberCount> }
|
||||||
|
</div>
|
||||||
|
};
|
||||||
|
|
||||||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const myMembership = useMyRoomMembership(space);
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
@ -158,43 +193,13 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
joinButtons = <InlineSpinner />;
|
joinButtons = <InlineSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let visibilitySection;
|
|
||||||
if (space.getJoinRule() === "public") {
|
|
||||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_public">
|
|
||||||
{ _t("Public space") }
|
|
||||||
</span>;
|
|
||||||
} else {
|
|
||||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_private">
|
|
||||||
{ _t("Private space") }
|
|
||||||
</span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_preview">
|
return <div className="mx_SpaceRoomView_preview">
|
||||||
{ inviterSection }
|
{ inviterSection }
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
<h1 className="mx_SpaceRoomView_preview_name">
|
<h1 className="mx_SpaceRoomView_preview_name">
|
||||||
<RoomName room={space} />
|
<RoomName room={space} />
|
||||||
</h1>
|
</h1>
|
||||||
<div className="mx_SpaceRoomView_preview_info">
|
<SpaceInfo space={space} />
|
||||||
{ visibilitySection }
|
|
||||||
<RoomMemberCount room={space}>
|
|
||||||
{(count) => count > 0 ? (
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_SpaceRoomView_preview_memberCount"
|
|
||||||
kind="link"
|
|
||||||
onClick={() => {
|
|
||||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
|
||||||
action: Action.SetRightPanelPhase,
|
|
||||||
phase: RightPanelPhases.RoomMemberList,
|
|
||||||
refireParams: { space },
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ _t("%(count)s members", { count }) }
|
|
||||||
</AccessibleButton>
|
|
||||||
) : null}
|
|
||||||
</RoomMemberCount>
|
|
||||||
</div>
|
|
||||||
<RoomTopic room={space}>
|
<RoomTopic room={space}>
|
||||||
{(topic, ref) =>
|
{(topic, ref) =>
|
||||||
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
|
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
|
||||||
|
@ -202,6 +207,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</RoomTopic>
|
</RoomTopic>
|
||||||
|
{ space.getJoinRule() === "public" && <FacePile room={space} /> }
|
||||||
<div className="mx_SpaceRoomView_preview_joinButtons">
|
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||||
{ joinButtons }
|
{ joinButtons }
|
||||||
</div>
|
</div>
|
||||||
|
@ -216,10 +222,14 @@ const SpaceLanding = ({ space }) => {
|
||||||
let inviteButton;
|
let inviteButton;
|
||||||
if (myMembership === "join" && space.canInvite(userId)) {
|
if (myMembership === "join" && space.canInvite(userId)) {
|
||||||
inviteButton = (
|
inviteButton = (
|
||||||
<AccessibleButton className="mx_SpaceRoomView_landing_inviteButton" onClick={() => {
|
<AccessibleButton
|
||||||
|
kind="primary"
|
||||||
|
className="mx_SpaceRoomView_landing_inviteButton"
|
||||||
|
onClick={() => {
|
||||||
showRoomInviteDialog(space.roomId);
|
showRoomInviteDialog(space.roomId);
|
||||||
}}>
|
}}
|
||||||
{ _t("Invite people") }
|
>
|
||||||
|
{ _t("Invite") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -256,36 +266,13 @@ const SpaceLanding = ({ space }) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [rooms, relations, viaMap] = useSpaceSummary(cli, space, refreshToken);
|
const onMembersClick = () => {
|
||||||
const [roomsMap, numRooms] = useMemo(() => {
|
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||||
if (!rooms) return [];
|
action: Action.SetRightPanelPhase,
|
||||||
const roomsMap = new Map<string, ISpaceSummaryRoom>(rooms.map(r => [r.room_id, r]));
|
phase: RightPanelPhases.RoomMemberList,
|
||||||
const numRooms = rooms.filter(r => r.room_type !== RoomType.Space).length;
|
refireParams: { space },
|
||||||
return [roomsMap, numRooms];
|
});
|
||||||
}, [rooms]);
|
};
|
||||||
|
|
||||||
let previewRooms;
|
|
||||||
if (roomsMap) {
|
|
||||||
previewRooms = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
|
||||||
<div className="mx_SpaceRoomDirectory_roomCount">
|
|
||||||
<h3>{ myMembership === "join" ? _t("Rooms") : _t("Default Rooms")}</h3>
|
|
||||||
<span>{ numRooms }</span>
|
|
||||||
</div>
|
|
||||||
<HierarchyLevel
|
|
||||||
spaceId={space.roomId}
|
|
||||||
rooms={roomsMap}
|
|
||||||
relations={relations}
|
|
||||||
parents={new Set()}
|
|
||||||
onViewRoomClick={(roomId, autoJoin) => {
|
|
||||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</AutoHideScrollbar>;
|
|
||||||
} else if (!rooms) {
|
|
||||||
previewRooms = <InlineSpinner />;
|
|
||||||
} else {
|
|
||||||
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_landing">
|
return <div className="mx_SpaceRoomView_landing">
|
||||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||||
|
@ -294,45 +281,26 @@ const SpaceLanding = ({ space }) => {
|
||||||
{(name) => {
|
{(name) => {
|
||||||
const tags = { name: () => <div className="mx_SpaceRoomView_landing_nameRow">
|
const tags = { name: () => <div className="mx_SpaceRoomView_landing_nameRow">
|
||||||
<h1>{ name }</h1>
|
<h1>{ name }</h1>
|
||||||
<RoomMemberCount room={space}>
|
|
||||||
{(count) => count > 0 ? (
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_SpaceRoomView_landing_memberCount"
|
|
||||||
kind="link"
|
|
||||||
onClick={() => {
|
|
||||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
|
||||||
action: Action.SetRightPanelPhase,
|
|
||||||
phase: RightPanelPhases.RoomMemberList,
|
|
||||||
refireParams: { space },
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{ _t("%(count)s members", { count }) }
|
|
||||||
</AccessibleButton>
|
|
||||||
) : null}
|
|
||||||
</RoomMemberCount>
|
|
||||||
</div> };
|
</div> };
|
||||||
if (shouldShowSpaceSettings(cli, space)) {
|
|
||||||
if (space.getJoinRule() === "public") {
|
|
||||||
return _t("Your public space <name/>", {}, tags) as JSX.Element;
|
|
||||||
} else {
|
|
||||||
return _t("Your private space <name/>", {}, tags) as JSX.Element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _t("Welcome to <name/>", {}, tags) as JSX.Element;
|
return _t("Welcome to <name/>", {}, tags) as JSX.Element;
|
||||||
}}
|
}}
|
||||||
</RoomName>
|
</RoomName>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mx_SpaceRoomView_landing_info">
|
||||||
|
<SpaceInfo space={space} />
|
||||||
|
<FacePile room={space} onlyKnownUsers={false} numShown={7} onClick={onMembersClick} />
|
||||||
|
{ inviteButton }
|
||||||
|
</div>
|
||||||
<div className="mx_SpaceRoomView_landing_topic">
|
<div className="mx_SpaceRoomView_landing_topic">
|
||||||
<RoomTopic room={space} />
|
<RoomTopic room={space} />
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
<div className="mx_SpaceRoomView_landing_adminButtons">
|
<div className="mx_SpaceRoomView_landing_adminButtons">
|
||||||
{ inviteButton }
|
|
||||||
{ addRoomButtons }
|
{ addRoomButtons }
|
||||||
{ settingsButton }
|
{ settingsButton }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ previewRooms }
|
<SpaceHierarchy space={space} showRoom={showRoom} refreshToken={refreshToken} />
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -675,9 +643,13 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
case Phase.PublicCreateRooms:
|
case Phase.PublicCreateRooms:
|
||||||
return <SpaceSetupFirstRooms
|
return <SpaceSetupFirstRooms
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
title={_t("What are some things you want to discuss?")}
|
title={_t("What are some things you want to discuss in %(spaceName)s?", {
|
||||||
description={_t("Let's create a room for each of them. " +
|
spaceName: this.props.space.name,
|
||||||
"You can add more later too, including already existing ones.")}
|
})}
|
||||||
|
description={
|
||||||
|
_t("Let's create a room for each of them.") + "\n" +
|
||||||
|
_t("You can add more later too, including already existing ones.")
|
||||||
|
}
|
||||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||||
/>;
|
/>;
|
||||||
case Phase.PublicShare:
|
case Phase.PublicShare:
|
||||||
|
|
|
@ -126,8 +126,8 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||||
<div>
|
<div>
|
||||||
{ _t("Make this space private") }
|
{ _t("Make this space private") }
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={joinRule === "private"}
|
checked={joinRule !== "public"}
|
||||||
onChange={checked => setJoinRule(checked ? "private" : "invite")}
|
onChange={checked => setJoinRule(checked ? "invite" : "public")}
|
||||||
disabled={!canSetJoinRule}
|
disabled={!canSetJoinRule}
|
||||||
aria-label={_t("Make this space private")}
|
aria-label={_t("Make this space private")}
|
||||||
/>
|
/>
|
||||||
|
|
66
src/components/views/elements/FacePile.tsx
Normal file
66
src/components/views/elements/FacePile.tsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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, { HTMLAttributes } from "react";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { sortBy } from "lodash";
|
||||||
|
|
||||||
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
|
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||||
|
import { useRoomMembers } from "../../../hooks/useRoomMembers";
|
||||||
|
|
||||||
|
const DEFAULT_NUM_FACES = 5;
|
||||||
|
|
||||||
|
interface IProps extends HTMLAttributes<HTMLSpanElement> {
|
||||||
|
room: Room;
|
||||||
|
onlyKnownUsers?: boolean;
|
||||||
|
numShown?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isKnownMember = (member: RoomMember) => !!DMRoomMap.shared().getDMRoomsForUserId(member.userId)?.length;
|
||||||
|
|
||||||
|
const FacePile = ({ room, onlyKnownUsers = true, numShown = DEFAULT_NUM_FACES, ...props }: IProps) => {
|
||||||
|
let members = useRoomMembers(room);
|
||||||
|
|
||||||
|
// sort users with an explicit avatar first
|
||||||
|
const iteratees = [member => !!member.getMxcAvatarUrl()];
|
||||||
|
if (onlyKnownUsers) {
|
||||||
|
members = members.filter(isKnownMember);
|
||||||
|
} else {
|
||||||
|
// sort known users first
|
||||||
|
iteratees.unshift(member => isKnownMember(member));
|
||||||
|
}
|
||||||
|
if (members.length < 1) return null;
|
||||||
|
|
||||||
|
const shownMembers = sortBy(members, iteratees).slice(0, numShown);
|
||||||
|
return <div {...props} className="mx_FacePile">
|
||||||
|
<div className="mx_FacePile_faces">
|
||||||
|
{ shownMembers.map(member => {
|
||||||
|
return <TextWithTooltip key={member.userId} tooltip={member.name}>
|
||||||
|
<MemberAvatar member={member} width={28} height={28} />
|
||||||
|
</TextWithTooltip>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
{ onlyKnownUsers && <span>
|
||||||
|
{ _t("%(count)s people you know have already joined", { count: members.length }) }
|
||||||
|
</span> }
|
||||||
|
</div>
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FacePile;
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -29,6 +29,7 @@ import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import {showSpaceInvite} from "../../../utils/space";
|
||||||
|
|
||||||
const NewRoomIntro = () => {
|
const NewRoomIntro = () => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
|
@ -116,7 +117,7 @@ const NewRoomIntro = () => {
|
||||||
className="mx_NewRoomIntro_inviteButton"
|
className="mx_NewRoomIntro_inviteButton"
|
||||||
kind="primary"
|
kind="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dis.dispatch({ action: "view_invite", roomId });
|
showSpaceInvite(parentSpace);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })}
|
{_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })}
|
||||||
|
|
|
@ -50,14 +50,10 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
import CallHandler from "../../../CallHandler";
|
import CallHandler from "../../../CallHandler";
|
||||||
import SpaceStore, {SUGGESTED_ROOMS} from "../../../stores/SpaceStore";
|
import SpaceStore, {SUGGESTED_ROOMS} from "../../../stores/SpaceStore";
|
||||||
import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space";
|
import {showAddExistingRooms, showCreateNewRoom, showSpaceInvite} from "../../../utils/space";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import { ISpaceSummaryRoom } from "../../structures/SpaceRoomDirectory";
|
import { ISpaceSummaryRoom } from "../../structures/SpaceRoomDirectory";
|
||||||
import { showRoomInviteDialog } from "../../../RoomInvite";
|
|
||||||
import Modal from "../../../Modal";
|
|
||||||
import SpacePublicShare from "../spaces/SpacePublicShare";
|
|
||||||
import InfoDialog from "../dialogs/InfoDialog";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||||
|
@ -431,21 +427,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
private onSpaceInviteClick = () => {
|
private onSpaceInviteClick = () => {
|
||||||
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search;
|
||||||
if (this.props.activeSpace.getJoinRule() === "public") {
|
showSpaceInvite(this.props.activeSpace, initialText);
|
||||||
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
|
||||||
title: _t("Invite to %(spaceName)s", { spaceName: this.props.activeSpace.name }),
|
|
||||||
description: <React.Fragment>
|
|
||||||
<span>{ _t("Share your public space") }</span>
|
|
||||||
<SpacePublicShare space={this.props.activeSpace} onFinished={() => modal.close()} />
|
|
||||||
</React.Fragment>,
|
|
||||||
fixedWidth: false,
|
|
||||||
button: false,
|
|
||||||
className: "mx_SpacePanel_sharePublicSpace",
|
|
||||||
hasCloseButton: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showRoomInviteDialog(this.props.activeSpace.roomId, initialText);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
|
private renderSuggestedRooms(): ReactComponentElement<typeof ExtraTile>[] {
|
||||||
|
|
|
@ -148,7 +148,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
|
|
||||||
<SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} />
|
<SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} />
|
||||||
|
|
||||||
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name && !busy}>
|
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name || busy}>
|
||||||
{ busy ? _t("Creating...") : _t("Create") }
|
{ busy ? _t("Creating...") : _t("Create") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
|
@ -34,21 +34,17 @@ import {
|
||||||
shouldShowSpaceSettings,
|
shouldShowSpaceSettings,
|
||||||
showAddExistingRooms,
|
showAddExistingRooms,
|
||||||
showCreateNewRoom,
|
showCreateNewRoom,
|
||||||
|
showSpaceInvite,
|
||||||
showSpaceSettings,
|
showSpaceSettings,
|
||||||
} from "../../../utils/space";
|
} from "../../../utils/space";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
|
import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import Modal from "../../../Modal";
|
|
||||||
import SpacePublicShare from "./SpacePublicShare";
|
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||||
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||||
import {showRoomInviteDialog} from "../../../RoomInvite";
|
|
||||||
import InfoDialog from "../dialogs/InfoDialog";
|
|
||||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||||
import SpaceRoomDirectory from "../../structures/SpaceRoomDirectory";
|
|
||||||
|
|
||||||
interface IItemProps {
|
interface IItemProps {
|
||||||
space?: Room;
|
space?: Room;
|
||||||
|
@ -115,36 +111,11 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
this.setState({contextMenuPosition: null});
|
this.setState({contextMenuPosition: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onHomeClick = (ev: ButtonEvent) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
defaultDispatcher.dispatch({
|
|
||||||
action: "view_room",
|
|
||||||
room_id: this.props.space.roomId,
|
|
||||||
});
|
|
||||||
this.setState({contextMenuPosition: null}); // also close the menu
|
|
||||||
};
|
|
||||||
|
|
||||||
private onInviteClick = (ev: ButtonEvent) => {
|
private onInviteClick = (ev: ButtonEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
if (this.props.space.getJoinRule() === "public") {
|
showSpaceInvite(this.props.space);
|
||||||
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
|
||||||
title: _t("Invite to %(spaceName)s", { spaceName: this.props.space.name }),
|
|
||||||
description: <React.Fragment>
|
|
||||||
<span>{ _t("Share your public space") }</span>
|
|
||||||
<SpacePublicShare space={this.props.space} onFinished={() => modal.close()} />
|
|
||||||
</React.Fragment>,
|
|
||||||
fixedWidth: false,
|
|
||||||
button: false,
|
|
||||||
className: "mx_SpacePanel_sharePublicSpace",
|
|
||||||
hasCloseButton: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showRoomInviteDialog(this.props.space.roomId);
|
|
||||||
}
|
|
||||||
this.setState({contextMenuPosition: null}); // also close the menu
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -206,9 +177,10 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
Modal.createTrackedDialog("Space room directory", "Space panel", SpaceRoomDirectory, {
|
defaultDispatcher.dispatch({
|
||||||
space: this.props.space,
|
action: "view_room",
|
||||||
}, "mx_SpaceRoomDirectory_dialogWrapper", false, true);
|
room_id: this.props.space.roomId,
|
||||||
|
});
|
||||||
this.setState({contextMenuPosition: null}); // also close the menu
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -249,6 +221,8 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
</IconizedContextMenuOptionList>;
|
</IconizedContextMenuOptionList>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canAddRooms = this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||||
|
|
||||||
let newRoomSection;
|
let newRoomSection;
|
||||||
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
||||||
newRoomSection = <IconizedContextMenuOptionList first>
|
newRoomSection = <IconizedContextMenuOptionList first>
|
||||||
|
@ -276,11 +250,6 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
</div>
|
</div>
|
||||||
<IconizedContextMenuOptionList first>
|
<IconizedContextMenuOptionList first>
|
||||||
{ inviteOption }
|
{ inviteOption }
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_SpacePanel_iconHome"
|
|
||||||
label={_t("Space Home")}
|
|
||||||
onClick={this.onHomeClick}
|
|
||||||
/>
|
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_SpacePanel_iconMembers"
|
iconClassName="mx_SpacePanel_iconMembers"
|
||||||
label={_t("Members")}
|
label={_t("Members")}
|
||||||
|
@ -289,7 +258,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||||
{ settingsOption }
|
{ settingsOption }
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_SpacePanel_iconExplore"
|
iconClassName="mx_SpacePanel_iconExplore"
|
||||||
label={_t("Explore rooms")}
|
label={canAddRooms ? _t("Manage & explore rooms") : _t("Explore rooms")}
|
||||||
onClick={this.onExploreRoomsClick}
|
onClick={this.onExploreRoomsClick}
|
||||||
/>
|
/>
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
|
|
|
@ -723,6 +723,8 @@
|
||||||
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
|
||||||
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
|
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
|
||||||
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
|
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
|
||||||
|
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
||||||
|
"Share your public space": "Share your public space",
|
||||||
"Unknown App": "Unknown App",
|
"Unknown App": "Unknown App",
|
||||||
"Help us improve %(brand)s": "Help us improve %(brand)s",
|
"Help us improve %(brand)s": "Help us improve %(brand)s",
|
||||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.",
|
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||||
|
@ -1012,14 +1014,12 @@
|
||||||
"Share invite link": "Share invite link",
|
"Share invite link": "Share invite link",
|
||||||
"Invite people": "Invite people",
|
"Invite people": "Invite people",
|
||||||
"Invite with email or username": "Invite with email or username",
|
"Invite with email or username": "Invite with email or username",
|
||||||
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
|
|
||||||
"Share your public space": "Share your public space",
|
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"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",
|
||||||
"Space Home": "Space Home",
|
|
||||||
"Members": "Members",
|
"Members": "Members",
|
||||||
|
"Manage & explore rooms": "Manage & explore rooms",
|
||||||
"Explore rooms": "Explore rooms",
|
"Explore rooms": "Explore rooms",
|
||||||
"Space options": "Space options",
|
"Space options": "Space options",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
|
@ -1910,6 +1910,8 @@
|
||||||
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.": "Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
|
||||||
"collapse": "collapse",
|
"collapse": "collapse",
|
||||||
"expand": "expand",
|
"expand": "expand",
|
||||||
|
"%(count)s people you know have already joined|other": "%(count)s people you know have already joined",
|
||||||
|
"%(count)s people you know have already joined|one": "%(count)s person you know has already joined",
|
||||||
"You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)",
|
"You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)",
|
||||||
"Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s",
|
"Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s",
|
||||||
"Rotate Left": "Rotate Left",
|
"Rotate Left": "Rotate Left",
|
||||||
|
@ -2610,7 +2612,6 @@
|
||||||
"Drop file here to upload": "Drop file here to upload",
|
"Drop file here to upload": "Drop file here to upload",
|
||||||
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
|
"You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.",
|
||||||
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
|
"You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.",
|
||||||
"Open": "Open",
|
|
||||||
"You don't have permission": "You don't have permission",
|
"You don't have permission": "You don't have permission",
|
||||||
"%(count)s members|other": "%(count)s members",
|
"%(count)s members|other": "%(count)s members",
|
||||||
"%(count)s members|one": "%(count)s member",
|
"%(count)s members|one": "%(count)s member",
|
||||||
|
@ -2618,7 +2619,6 @@
|
||||||
"%(count)s rooms|one": "%(count)s room",
|
"%(count)s rooms|one": "%(count)s room",
|
||||||
"This room is suggested as a good one to join": "This room is suggested as a good one to join",
|
"This room is suggested as a good one to join": "This room is suggested as a good one to join",
|
||||||
"Suggested": "Suggested",
|
"Suggested": "Suggested",
|
||||||
"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>.",
|
|
||||||
"%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s rooms and %(numSpaces)s spaces",
|
"%(count)s rooms and %(numSpaces)s spaces|other": "%(count)s rooms and %(numSpaces)s spaces",
|
||||||
"%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s room and %(numSpaces)s spaces",
|
"%(count)s rooms and %(numSpaces)s spaces|one": "%(count)s room and %(numSpaces)s spaces",
|
||||||
"%(count)s rooms and 1 space|other": "%(count)s rooms and 1 space",
|
"%(count)s rooms and 1 space|other": "%(count)s rooms and 1 space",
|
||||||
|
@ -2629,16 +2629,14 @@
|
||||||
"Mark as suggested": "Mark as suggested",
|
"Mark as suggested": "Mark as suggested",
|
||||||
"No results found": "No results found",
|
"No results found": "No results found",
|
||||||
"You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.",
|
"You may want to try a different search or check for typos.": "You may want to try a different search or check for typos.",
|
||||||
"Create room": "Create room",
|
"Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.",
|
||||||
"Search names and description": "Search names and description",
|
"Search names and description": "Search names and description",
|
||||||
"<inviter/> invites you": "<inviter/> invites you",
|
"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",
|
||||||
"Public space": "Public space",
|
"Public space": "Public space",
|
||||||
"Private space": "Private space",
|
"Private space": "Private space",
|
||||||
|
"<inviter/> invites you": "<inviter/> invites you",
|
||||||
"Add existing rooms & spaces": "Add existing rooms & spaces",
|
"Add existing rooms & spaces": "Add existing rooms & spaces",
|
||||||
"Default Rooms": "Default Rooms",
|
|
||||||
"Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.",
|
|
||||||
"Your public space <name/>": "Your public space <name/>",
|
|
||||||
"Your private space <name/>": "Your private space <name/>",
|
|
||||||
"Welcome to <name/>": "Welcome to <name/>",
|
"Welcome to <name/>": "Welcome to <name/>",
|
||||||
"Random": "Random",
|
"Random": "Random",
|
||||||
"Support": "Support",
|
"Support": "Support",
|
||||||
|
@ -2660,8 +2658,9 @@
|
||||||
"Invite your teammates": "Invite your teammates",
|
"Invite your teammates": "Invite your teammates",
|
||||||
"Make sure the right people have access. You can invite more later.": "Make sure the right people have access. You can invite more later.",
|
"Make sure the right people have access. You can invite more later.": "Make sure the right people have access. You can invite more later.",
|
||||||
"Invite by username": "Invite by username",
|
"Invite by username": "Invite by username",
|
||||||
"What are some things you want to discuss?": "What are some things you want to discuss?",
|
"What are some things you want to discuss in %(spaceName)s?": "What are some things you want to discuss in %(spaceName)s?",
|
||||||
"Let's create a room for each of them. You can add more later too, including already existing ones.": "Let's create a room for each of them. You can add more later too, including already existing ones.",
|
"Let's create a room for each of them.": "Let's create a room for each of them.",
|
||||||
|
"You can add more later too, including already existing ones.": "You can add more later too, including already existing ones.",
|
||||||
"What projects are you working on?": "What projects are you working on?",
|
"What projects are you working on?": "What projects are you working on?",
|
||||||
"We'll create rooms for each of them. You can add more later too, including already existing ones.": "We'll create rooms for each of them. You can add more later too, including already existing ones.",
|
"We'll create rooms for each of them. You can add more later too, including already existing ones.": "We'll create rooms for each of them. You can add more later too, including already existing ones.",
|
||||||
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
|
"Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.",
|
||||||
|
|
|
@ -122,7 +122,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
const data = await this.fetchSuggestedRooms(space);
|
const data = await this.fetchSuggestedRooms(space);
|
||||||
if (this._activeSpace === space) {
|
if (this._activeSpace === space) {
|
||||||
this._suggestedRooms = data.rooms.filter(roomInfo => {
|
this._suggestedRooms = data.rooms.filter(roomInfo => {
|
||||||
return roomInfo.room_type !== RoomType.Space && !this.matrixClient.getRoom(roomInfo.room_id);
|
return roomInfo.room_type !== RoomType.Space
|
||||||
|
&& this.matrixClient.getRoom(roomInfo.room_id)?.getMyMembership() !== "join";
|
||||||
});
|
});
|
||||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||||
}
|
}
|
||||||
|
@ -294,6 +295,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onSpaceMembersChange = (ev: MatrixEvent) => {
|
||||||
|
// skip this update if we do not have a DM with this user
|
||||||
|
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
|
||||||
|
this.onRoomsUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
private onRoomsUpdate = throttle(() => {
|
private onRoomsUpdate = throttle(() => {
|
||||||
// TODO resolve some updates as deltas
|
// TODO resolve some updates as deltas
|
||||||
const visibleRooms = this.matrixClient.getVisibleRooms();
|
const visibleRooms = this.matrixClient.getVisibleRooms();
|
||||||
|
@ -374,21 +381,28 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.setActiveSpace(room);
|
this.setActiveSpace(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (room.getMyMembership() === "join") {
|
||||||
const numSuggestedRooms = this._suggestedRooms.length;
|
const numSuggestedRooms = this._suggestedRooms.length;
|
||||||
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
|
this._suggestedRooms = this._suggestedRooms.filter(r => r.room_id !== room.roomId);
|
||||||
if (numSuggestedRooms !== this._suggestedRooms.length) {
|
if (numSuggestedRooms !== this._suggestedRooms.length) {
|
||||||
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
this.emit(SUGGESTED_ROOMS, this._suggestedRooms);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomState = (ev: MatrixEvent) => {
|
private onRoomState = (ev: MatrixEvent) => {
|
||||||
const room = this.matrixClient.getRoom(ev.getRoomId());
|
const room = this.matrixClient.getRoom(ev.getRoomId());
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
|
|
||||||
if (ev.getType() === EventType.SpaceChild && room.isSpaceRoom()) {
|
switch (ev.getType()) {
|
||||||
|
case EventType.SpaceChild:
|
||||||
|
if (room.isSpaceRoom()) {
|
||||||
this.onSpaceUpdate();
|
this.onSpaceUpdate();
|
||||||
this.emit(room.roomId);
|
this.emit(room.roomId);
|
||||||
} else if (ev.getType() === EventType.SpaceParent) {
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EventType.SpaceParent:
|
||||||
// TODO rebuild the space parent and not the room - check permissions?
|
// TODO rebuild the space parent and not the room - check permissions?
|
||||||
// TODO confirm this after implementing parenting behaviour
|
// TODO confirm this after implementing parenting behaviour
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
|
@ -397,6 +411,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.onRoomUpdate(room);
|
this.onRoomUpdate(room);
|
||||||
}
|
}
|
||||||
this.emit(room.roomId);
|
this.emit(room.roomId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EventType.RoomMember:
|
||||||
|
if (room.isSpaceRoom()) {
|
||||||
|
this.onSpaceMembersChange(ev);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React 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 {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
@ -24,6 +25,10 @@ import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog
|
||||||
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
|
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
|
||||||
import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
|
import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog";
|
||||||
import createRoom, {IOpts} from "../createRoom";
|
import createRoom, {IOpts} from "../createRoom";
|
||||||
|
import {_t} from "../languageHandler";
|
||||||
|
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
|
||||||
|
import InfoDialog from "../components/views/dialogs/InfoDialog";
|
||||||
|
import { showRoomInviteDialog } from "../RoomInvite";
|
||||||
|
|
||||||
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
|
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
|
@ -79,3 +84,21 @@ export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
|
||||||
await createRoom(opts);
|
await createRoom(opts);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const showSpaceInvite = (space: Room, initialText = "") => {
|
||||||
|
if (space.getJoinRule() === "public") {
|
||||||
|
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
|
||||||
|
title: _t("Invite to %(spaceName)s", { spaceName: space.name }),
|
||||||
|
description: <React.Fragment>
|
||||||
|
<span>{ _t("Share your public space") }</span>
|
||||||
|
<SpacePublicShare space={space} onFinished={() => modal.close()} />
|
||||||
|
</React.Fragment>,
|
||||||
|
fixedWidth: false,
|
||||||
|
button: false,
|
||||||
|
className: "mx_SpacePanel_sharePublicSpace",
|
||||||
|
hasCloseButton: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showRoomInviteDialog(space.roomId, initialText);
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in a new issue