diff --git a/res/css/_components.scss b/res/css/_components.scss index 20b2461960..1feea1d26f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -76,6 +76,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/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 195c31e959..7d74d7d5a7 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 { IRoomChild, IRoomChildState } 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: IRoomChild, 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, IRoomChild[], 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 { rooms } = await cli.getRoomChildren(space.roomId); + const { rooms } = await space.client.getRoomChildren(space.roomId); const parentChildRelations = new EnhancedMap>(); const childParentRelations = new EnhancedMap>(); @@ -357,7 +365,6 @@ export const SpaceHierarchy: React.FC = ({ space, initialText = "", showRoom, - refreshToken, additionalButtons, children, }) => { @@ -367,7 +374,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 a077fddadf..5829578cd2 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, @@ -307,7 +317,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ; }; -const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => { +const SpaceLandingAddButton = ({ space }) => { const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); let contextMenu; @@ -331,24 +341,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); + }} + > + + ; } @@ -389,11 +407,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; @@ -443,12 +459,7 @@ const SpaceLanding = ({ space }) => {
- + ; }; @@ -550,10 +561,12 @@ 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/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index 1d822fd246..c9506d7c98 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -90,10 +90,11 @@ export const IconizedContextMenuCheckbox: React.FC = ({ ; }; -export const IconizedContextMenuOption: React.FC = ({ label, iconClassName, ...props }) => { +export const IconizedContextMenuOption: React.FC = ({ label, iconClassName, children, ...props }) => { return { iconClassName && } { label } + { children } ; }; diff --git a/src/components/views/dialogs/AddExistingSubspaceDialog.tsx b/src/components/views/dialogs/AddExistingSubspaceDialog.tsx new file mode 100644 index 0000000000..f0e5e9241e --- /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 subspace") } + + } + 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 3ef86e438d..9b64af0c80 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"; @@ -42,12 +42,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