diff --git a/res/css/_components.scss b/res/css/_components.scss
index 7eb1208d50..014c295025 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -75,6 +75,7 @@
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
@import "./views/dialogs/_CreateGroupDialog.scss";
@import "./views/dialogs/_CreateRoomDialog.scss";
+@import "./views/dialogs/_CreateSubspaceDialog.scss";
@import "./views/dialogs/_DeactivateAccountDialog.scss";
@import "./views/dialogs/_DevtoolsDialog.scss";
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
@@ -85,6 +86,7 @@
@import "./views/dialogs/_HostSignupDialog.scss";
@import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_InviteDialog.scss";
+@import "./views/dialogs/_JoinRuleDropdown.scss";
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
@import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss";
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss
index 204435995f..2aa43283b5 100644
--- a/res/css/views/context_menus/_IconizedContextMenu.scss
+++ b/res/css/views/context_menus/_IconizedContextMenu.scss
@@ -99,6 +99,10 @@ limitations under the License.
.mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label {
padding-left: 14px;
}
+
+ .mx_BetaCard_betaPill {
+ margin-left: 16px;
+ }
}
}
diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
index 2776c477fc..b299198349 100644
--- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
+++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
@@ -54,11 +54,16 @@ limitations under the License.
display: flex;
margin-top: 12px;
- // we can't target .mx_BaseAvatar here as it'll break the decorated avatar styling
- .mx_DecoratedRoomAvatar {
+ .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;
@@ -73,41 +78,12 @@ limitations under the License.
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;
- line-height: $font-15px;
- color: $secondary-fg-color;
-
- &::before {
- content: '';
- position: absolute;
- left: 10px;
- top: calc(50% - 8px); // vertical centering
- height: 16px;
- width: 16px;
- background-color: $secondary-fg-color;
- mask-repeat: no-repeat;
- mask-size: contain;
- mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
- mask-position: center;
+ .mx_AccessibleButton_kind_link {
+ font-size: $font-12px;
+ line-height: $font-15px;
+ margin-top: 8px;
+ padding: 0;
}
}
@@ -205,77 +181,77 @@ limitations under the License.
min-height: 0;
height: 80vh;
- .mx_Dialog_title {
- display: flex;
-
- .mx_BaseAvatar_image {
- border-radius: 8px;
- margin: 0;
- vertical-align: unset;
- }
-
- .mx_BaseAvatar {
- display: inline-flex;
- margin: auto 16px auto 5px;
- vertical-align: middle;
- }
-
- > div {
- > h1 {
- font-weight: $font-semi-bold;
- font-size: $font-18px;
- line-height: $font-22px;
- margin: 0;
- }
-
- .mx_AddExistingToSpaceDialog_onlySpace {
- color: $secondary-fg-color;
- font-size: $font-15px;
- line-height: $font-24px;
- }
- }
-
- .mx_Dropdown_input {
- border: none;
-
- > .mx_Dropdown_option {
- padding-left: 0;
- flex: unset;
- height: unset;
- color: $secondary-fg-color;
- font-size: $font-15px;
- line-height: $font-24px;
-
- .mx_BaseAvatar {
- display: none;
- }
- }
-
- .mx_Dropdown_menu {
- .mx_AddExistingToSpaceDialog_dropdownOptionActive {
- color: $accent-color;
- padding-right: 32px;
- position: relative;
-
- &::before {
- content: '';
- width: 20px;
- height: 20px;
- top: 8px;
- right: 0;
- position: absolute;
- mask-position: center;
- mask-size: contain;
- mask-repeat: no-repeat;
- background-color: $accent-color;
- mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
- }
- }
- }
- }
- }
-
.mx_AddExistingToSpace {
display: contents;
}
}
+
+.mx_SubspaceSelector {
+ display: flex;
+
+ .mx_BaseAvatar_image {
+ border-radius: 8px;
+ margin: 0;
+ vertical-align: unset;
+ }
+
+ .mx_BaseAvatar {
+ display: inline-flex;
+ margin: auto 16px auto 5px;
+ vertical-align: middle;
+ }
+
+ > div {
+ > h1 {
+ font-weight: $font-semi-bold;
+ font-size: $font-18px;
+ line-height: $font-22px;
+ margin: 0;
+ }
+ }
+
+ .mx_Dropdown_input {
+ border: none;
+
+ > .mx_Dropdown_option {
+ padding-left: 0;
+ flex: unset;
+ height: unset;
+ color: $secondary-fg-color;
+ font-size: $font-15px;
+ line-height: $font-24px;
+
+ .mx_BaseAvatar {
+ display: none;
+ }
+ }
+
+ .mx_Dropdown_menu {
+ .mx_SubspaceSelector_dropdownOptionActive {
+ color: $accent-color;
+ padding-right: 32px;
+ position: relative;
+
+ &::before {
+ content: '';
+ width: 20px;
+ height: 20px;
+ top: 8px;
+ right: 0;
+ position: absolute;
+ mask-position: center;
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ background-color: $accent-color;
+ mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg');
+ }
+ }
+ }
+ }
+
+ .mx_SubspaceSelector_onlySpace {
+ color: $secondary-fg-color;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ }
+}
diff --git a/res/css/views/dialogs/_CreateRoomDialog.scss b/res/css/views/dialogs/_CreateRoomDialog.scss
index 5321d8ff69..e7cfbf6050 100644
--- a/res/css/views/dialogs/_CreateRoomDialog.scss
+++ b/res/css/views/dialogs/_CreateRoomDialog.scss
@@ -109,56 +109,4 @@ limitations under the License.
margin: 0 85px 0 0;
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;
- }
- }
}
-
diff --git a/res/css/views/dialogs/_CreateSubspaceDialog.scss b/res/css/views/dialogs/_CreateSubspaceDialog.scss
new file mode 100644
index 0000000000..1ec4731ae6
--- /dev/null
+++ b/res/css/views/dialogs/_CreateSubspaceDialog.scss
@@ -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;
+ }
+ }
+}
diff --git a/res/css/views/dialogs/_JoinRuleDropdown.scss b/res/css/views/dialogs/_JoinRuleDropdown.scss
new file mode 100644
index 0000000000..c48a79af3c
--- /dev/null
+++ b/res/css/views/dialogs/_JoinRuleDropdown.scss
@@ -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;
+ }
+}
+
diff --git a/res/css/views/messages/_ViewSourceEvent.scss b/res/css/views/messages/_ViewSourceEvent.scss
index 66825030e0..b0e40a5152 100644
--- a/res/css/views/messages/_ViewSourceEvent.scss
+++ b/res/css/views/messages/_ViewSourceEvent.scss
@@ -43,8 +43,10 @@ limitations under the License.
margin-bottom: 7px;
mask-image: url('$(res)/img/feather-customised/minimise.svg');
}
+}
- &:hover .mx_ViewSourceEvent_toggle {
+.mx_EventTile:hover {
+ .mx_ViewSourceEvent_toggle {
visibility: visible;
}
}
diff --git a/res/css/views/rooms/_EventBubbleTile.scss b/res/css/views/rooms/_EventBubbleTile.scss
index 1d43c3030d..e495c2c596 100644
--- a/res/css/views/rooms/_EventBubbleTile.scss
+++ b/res/css/views/rooms/_EventBubbleTile.scss
@@ -38,18 +38,22 @@ limitations under the License.
padding-top: 0;
}
+ &::before {
+ content: '';
+ position: absolute;
+ top: -1px;
+ bottom: -1px;
+ left: -60px;
+ right: -60px;
+ z-index: -1;
+ border-radius: 4px;
+ }
+
&:hover,
&.mx_EventTile_selected {
+
&::before {
- content: '';
- position: absolute;
- top: -1px;
- bottom: -1px;
- left: -60px;
- right: -60px;
- z-index: -1;
background: $eventbubble-bg-hover;
- border-radius: 4px;
}
.mx_EventTile_avatar {
@@ -267,7 +271,7 @@ limitations under the License.
display: flex;
align-items: center;
- justify-content: center;
+ justify-content: start;
padding: 5px 0;
.mx_EventTile_avatar {
@@ -275,12 +279,22 @@ limitations under the License.
order: -1;
margin-right: 5px;
}
+
+ .mx_EventTile_e2eIcon {
+ margin-left: 9px;
+ }
+
+ .mx_EventTile_line > a {
+ right: auto;
+ top: -15px;
+ left: -68px;
+ }
}
.mx_EventListSummary[data-layout=bubble] {
- --maxWidth: 80%;
+ --maxWidth: 70%;
margin-left: calc(var(--avatarSize) + var(--gutterSize));
- margin-right: calc(var(--gutterSize) + var(--avatarSize));
+ margin-right: 94px;
.mx_EventListSummary_toggle {
float: none;
margin: 0;
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 206fe843de..5de9a9f9d1 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -59,7 +59,6 @@ $hover-select-border: 4px;
font-size: $font-14px;
display: inline-block; /* anti-zalgo, with overflow hidden */
overflow: hidden;
- cursor: pointer;
padding-bottom: 0px;
padding-top: 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
}
+.mx_SenderProfile {
+ cursor: pointer;
+}
+
.mx_EventTile_bubbleContainer {
display: grid;
grid-template-columns: 1fr 100px;
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 038c1df514..d8cc9593f0 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -16,7 +16,6 @@ limitations under the License.
import React, { ReactNode, useMemo, useState } from "react";
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 { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces";
import classNames from "classnames";
@@ -44,11 +43,13 @@ import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { linkifyElement } from "../../HtmlUtils";
import { getDisplayAliasForAliasSet } from "../../Rooms";
+import { useDispatcher } from "../../hooks/useDispatcher";
+import defaultDispatcher from "../../dispatcher/dispatcher";
+import { Action } from "../../dispatcher/actions";
interface IHierarchyProps {
space: Room;
initialText?: string;
- refreshToken?: any;
additionalButtons?: ReactNode;
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
}
@@ -315,18 +316,25 @@ export const HierarchyLevel = ({
;
};
-// mutate argument refreshToken to force a reload
-export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: any): [
+export const useSpaceSummary = (space: Room): [
null,
ISpaceSummaryRoom[],
Map>?,
Map>?,
Map>?,
] | [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
return useAsyncMemo(async () => {
try {
- const data = await cli.getSpaceSummary(space.roomId);
+ const data = await space.client.getSpaceSummary(space.roomId);
const parentChildRelations = new EnhancedMap>();
const childParentRelations = new EnhancedMap>();
@@ -354,7 +362,6 @@ export const SpaceHierarchy: React.FC = ({
space,
initialText = "",
showRoom,
- refreshToken,
additionalButtons,
children,
}) => {
@@ -364,7 +371,7 @@ export const SpaceHierarchy: React.FC = ({
const [selected, setSelected] = useState(new Map>()); // Map>
- const [summaryError, rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space, refreshToken);
+ const [summaryError, rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(space);
const roomsMap = useMemo(() => {
if (!rooms) return null;
diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx
index b47eb12c74..4064b2f48e 100644
--- a/src/components/structures/SpaceRoomView.tsx
+++ b/src/components/structures/SpaceRoomView.tsx
@@ -47,13 +47,23 @@ import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
import { SetRightPanelPhasePayload } from "../../dispatcher/payloads/SetRightPanelPhasePayload";
import { useStateArray } from "../../hooks/useStateArray";
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 MemberAvatar from "../views/avatars/MemberAvatar";
-import { useStateToggle } from "../../hooks/useStateToggle";
import SpaceStore from "../../stores/SpaceStore";
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 IconizedContextMenu, {
IconizedContextMenuOption,
@@ -283,7 +293,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
;
};
-const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
+const SpaceLandingAddButton = ({ space }) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
let contextMenu;
@@ -307,24 +317,32 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
closeMenu();
if (await showCreateNewRoom(space)) {
- onNewRoomAdded();
+ defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
}
}}
/>
{
+ onClick={(e) => {
e.preventDefault();
e.stopPropagation();
closeMenu();
-
- const [added] = await showAddExistingRooms(space);
- if (added) {
- onNewRoomAdded();
- }
+ showAddExistingRooms(space);
}}
/>
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ closeMenu();
+ showCreateNewSubspace(space);
+ }}
+ >
+
+
;
}
@@ -365,11 +383,9 @@ const SpaceLanding = ({ space }) => {
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
- const [refreshToken, forceUpdate] = useStateToggle(false);
-
let addRoomButton;
if (canAddRooms) {
- addRoomButton = ;
+ addRoomButton = ;
}
let settingsButton;
@@ -418,12 +434,7 @@ const SpaceLanding = ({ space }) => {
) }
-
+
;
};
@@ -524,7 +535,11 @@ const SpaceAddExistingRooms = ({ space, onFinished }) => {
{ _t("Skip for now") }
}
+ filterPlaceholder={_t("Search for rooms or spaces")}
onFinished={onFinished}
+ roomsRenderer={defaultRoomsRenderer}
+ spacesRenderer={defaultSpacesRenderer}
+ dmsRenderer={defaultDmsRenderer}
/>
;
};
diff --git a/src/components/views/auth/CaptchaForm.tsx b/src/components/views/auth/CaptchaForm.tsx
index b1c09f2b22..97f45167a8 100644
--- a/src/components/views/auth/CaptchaForm.tsx
+++ b/src/components/views/auth/CaptchaForm.tsx
@@ -103,8 +103,8 @@ export default class CaptchaForm extends React.Component = ({
;
};
-export const IconizedContextMenuOption: React.FC = ({ label, iconClassName, ...props }) => {
+export const IconizedContextMenuOption: React.FC = ({ label, iconClassName, children, ...props }) => {
return ;
};
diff --git a/src/components/views/dialogs/AddExistingSubspaceDialog.tsx b/src/components/views/dialogs/AddExistingSubspaceDialog.tsx
new file mode 100644
index 0000000000..a6dbf9dd42
--- /dev/null
+++ b/src/components/views/dialogs/AddExistingSubspaceDialog.tsx
@@ -0,0 +1,70 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import 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 { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
+import { AddExistingToSpace, defaultSpacesRenderer, SubspaceSelector } from "./AddExistingToSpaceDialog";
+
+interface IProps {
+ space: Room;
+ onCreateSubspaceClick(): void;
+ onFinished(added?: boolean): void;
+}
+
+const AddExistingSubspaceDialog: React.FC = ({ space, onCreateSubspaceClick, onFinished }) => {
+ const [selectedSpace, setSelectedSpace] = useState(space);
+
+ return
+ )}
+ className="mx_AddExistingToSpaceDialog"
+ contentId="mx_AddExistingToSpace"
+ onFinished={onFinished}
+ fixedWidth={false}
+ >
+
+
+
{ _t("Want to add a new space instead?") }
+
+ { _t("Create a new space") }
+
+ >}
+ filterPlaceholder={_t("Search for spaces")}
+ spacesRenderer={defaultSpacesRenderer}
+ />
+
+
+ onFinished(false)} />
+ ;
+};
+
+export default AddExistingSubspaceDialog;
+
diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
index 6f14c4a676..cf4f369d09 100644
--- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
+++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
@@ -18,9 +18,9 @@ import React, { ReactNode, useContext, useMemo, useState } from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import { sleep } from "matrix-js-sdk/src/utils";
+import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t } from '../../../languageHandler';
-import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
import Dropdown from "../elements/Dropdown";
import SearchBox from "../../structures/SearchBox";
@@ -41,12 +41,14 @@ import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
-interface IProps extends IDialogProps {
+interface IProps {
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
-
+
{ busy ? _t("Creating...") : _t("Create") }
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index 90584a5361..7b42367e31 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -34,6 +34,7 @@ import {
shouldShowSpaceSettings,
showAddExistingRooms,
showCreateNewRoom,
+ showCreateNewSubspace,
showSpaceInvite,
showSpaceSettings,
} from "../../../utils/space";
@@ -48,6 +49,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
+import { BetaPill } from "../beta/BetaCard";
interface IItemProps extends InputHTMLAttributes {
space?: Room;
@@ -234,6 +236,14 @@ export class SpaceItem extends React.PureComponent {
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) => {
ev.preventDefault();
ev.stopPropagation();
@@ -318,6 +328,13 @@ export class SpaceItem extends React.PureComponent {
label={_t("Add existing room")}
onClick={this.onAddExistingRoomClick}
/>
+
+
+
;
}
diff --git a/src/createRoom.ts b/src/createRoom.ts
index 613fe26c9e..a3b06fa8ba 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -18,9 +18,15 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
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 { 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 Modal from './Modal';
@@ -52,6 +58,9 @@ export interface IOpts {
inlineErrors?: boolean;
andView?: boolean;
associatedWithCommunity?: string;
+ avatar?: File | string; // will upload if given file, else mxcUrl is needed
+ roomType?: RoomType | string;
+ historyVisibility?: HistoryVisibility;
parentSpace?: Room;
joinRule?: JoinRule;
}
@@ -112,6 +121,13 @@ export default async function createRoom(opts: IOpts): Promise {
createOpts.is_direct = true;
}
+ if (opts.roomType) {
+ createOpts.creation_content = {
+ ...createOpts.creation_content,
+ [RoomCreateTypeField]: opts.roomType,
+ };
+ }
+
// By default, view the room after creating it
if (opts.andView === undefined) {
opts.andView = true;
@@ -144,12 +160,11 @@ export default async function createRoom(opts: IOpts): Promise {
if (opts.parentSpace) {
createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true));
- createOpts.initial_state.push({
- type: EventType.RoomHistoryVisibility,
- content: {
- "history_visibility": createOpts.preset === Preset.PublicChat ? "world_readable" : "invited",
- },
- });
+ if (!opts.historyVisibility) {
+ opts.historyVisibility = createOpts.preset === Preset.PublicChat
+ ? HistoryVisibility.WorldReadable
+ : HistoryVisibility.Invited;
+ }
if (opts.joinRule === JoinRule.Restricted) {
if (SpaceStore.instance.restrictedJoinRuleSupport?.preferred) {
@@ -176,6 +191,27 @@ export default async function createRoom(opts: IOpts): Promise {
});
}
+ 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;
if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 5732428201..06cbbba46c 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -193,4 +193,9 @@ export enum Action {
* Switches space. Should be used with SwitchSpacePayload.
*/
SwitchSpace = "switch_space",
+
+ /**
+ * Signals to the visible space hierarchy that a change has occurred an that it should refresh.
+ */
+ UpdateSpaceHierarchy = "update_space_hierarchy",
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e791eb4dae..901204d743 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1009,6 +1009,8 @@
"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",
"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",
@@ -1021,8 +1023,6 @@
"Your private space": "Your private space",
"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.",
- "e.g. my-space": "e.g. my-space",
- "Address": "Address",
"Creating...": "Creating...",
"Create": "Create",
"All rooms": "All rooms",
@@ -1060,6 +1060,7 @@
"Leave space": "Leave space",
"Create new room": "Create new room",
"Add existing room": "Add existing room",
+ "Add space": "Add space",
"Members": "Members",
"Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms",
@@ -2114,17 +2115,20 @@
"Add a new server...": "Add a new server...",
"%(networkName)s rooms": "%(networkName)s 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",
"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...",
- "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",
"Space selection": "Space selection",
"Add existing rooms": "Add existing rooms",
"Want to add a new room instead?": "Want to add a new room instead?",
"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 Room ID": "Matrix Room ID",
"email address": "email address",
@@ -2205,13 +2209,22 @@
"Create a room in %(communityName)s": "Create a room in %(communityName)s",
"Create a public room": "Create a public 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)",
"Public room": "Public room",
"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.",
"Create Room": "Create Room",
+ "Anyone in will be able to find and join.": "Anyone in will be able to find and join.",
+ "Anyone will be able to find and join this space, not just members of .": "Anyone will be able to find and join this space, not just members of .",
+ "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",
"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.",
@@ -2803,7 +2816,6 @@
"Search names and descriptions": "Search names and descriptions",
"If you can't find the room you're looking for, ask for an invite or create a new room.": "If you can't find the room you're looking for, ask for an invite or create a new room.",
"Create room": "Create room",
- "Public space": "Public space",
"Private space": "Private space",
" invites you": " invites you",
"To view %(spaceName)s, turn on the Spaces beta": "To view %(spaceName)s, turn on the Spaces beta",
@@ -2818,6 +2830,7 @@
"Creating rooms...": "Creating rooms...",
"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.",
+ "Search for rooms or spaces": "Search for rooms or spaces",
"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.",
"Go to my first room": "Go to my first room",
diff --git a/src/utils/space.tsx b/src/utils/space.tsx
index c238a83bc2..e705b4eee4 100644
--- a/src/utils/space.tsx
+++ b/src/utils/space.tsx
@@ -28,6 +28,11 @@ import { _t } from "../languageHandler";
import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
import InfoDialog from "../components/views/dialogs/InfoDialog";
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";
export const shouldShowSpaceSettings = (space: Room) => {
const userId = space.client.getUserId();
@@ -54,21 +59,26 @@ export const showSpaceSettings = (space: Room) => {
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
};
-export const showAddExistingRooms = async (space: Room) => {
- return Modal.createTrackedDialog(
+export const showAddExistingRooms = (space: Room): void => {
+ Modal.createTrackedDialog(
"Space Landing",
"Add Existing",
AddExistingToSpaceDialog,
{
- matrixClient: space.client,
- onCreateRoomClick: showCreateNewRoom,
+ onCreateRoomClick: () => showCreateNewRoom(space),
+ onAddSubspaceClick: () => showAddExistingSubspace(space),
space,
+ onFinished: (added: boolean) => {
+ if (added && RoomViewStore.getRoomId() === space.roomId) {
+ defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
+ }
+ },
},
"mx_AddExistingToSpaceDialog_wrapper",
- ).finished;
+ );
};
-export const showCreateNewRoom = async (space: Room) => {
+export const showCreateNewRoom = async (space: Room): Promise => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing",
"Create Room",
@@ -85,7 +95,7 @@ export const showCreateNewRoom = async (space: Room) => {
return shouldCreate;
};
-export const showSpaceInvite = (space: Room, initialText = "") => {
+export const showSpaceInvite = (space: Room, initialText = ""): void => {
if (space.getJoinRule() === "public") {
const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, {
title: _t("Invite to %(spaceName)s", { spaceName: space.name }),
@@ -102,3 +112,39 @@ export const showSpaceInvite = (space: Room, 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",
+ );
+};