diff --git a/res/css/structures/_GroupFilterPanel.scss b/res/css/structures/_GroupFilterPanel.scss index cc0e760031..ceea20ed79 100644 --- a/res/css/structures/_GroupFilterPanel.scss +++ b/res/css/structures/_GroupFilterPanel.scss @@ -73,12 +73,6 @@ $groupFilterPanelWidth: 56px; // only applies in this file, used for calculation .mx_GroupFilterPanel .mx_TagTile { // opacity: 0.5; position: relative; - - .mx_BetaDot { - position: absolute; - right: -13px; - top: -11px; - } } .mx_GroupFilterPanel .mx_TagTile.mx_TagTile_prototype { diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 29c8c0c36d..30d421a351 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -103,6 +103,16 @@ $activeBorderColor: $secondary-content; } } + .mx_SpaceItem_new { + position: relative; + + .mx_BetaDot { + position: absolute; + left: 33px; + top: -5px; + } + } + .mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton { margin-left: $gutterSize; min-width: 40px; @@ -194,22 +204,17 @@ $activeBorderColor: $secondary-content; } &.mx_SpaceButton_new .mx_SpaceButton_icon { - background-color: $accent-color; - transition: all .1s ease-in-out; // TODO transition + background-color: $roomlist-button-bg-color; &::before { - background-color: #ffffff; + background-color: $primary-content; mask-image: url('$(res)/img/element-icons/plus.svg'); transition: all .2s ease-in-out; // TODO transition } } - &.mx_SpaceButton_newCancel .mx_SpaceButton_icon { - background-color: $icon-button-color; - - &::before { - transform: rotate(45deg); - } + &.mx_SpaceButton_newCancel .mx_SpaceButton_icon::before { + transform: rotate(45deg); } .mx_BaseAvatar_image { diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 39eabe2e07..812b6dcea9 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -215,9 +215,10 @@ $SpaceRoomViewInnerWidth: 428px; } } - > .mx_BaseAvatar_image, - > .mx_BaseAvatar > .mx_BaseAvatar_image { - border-radius: 12px; + > .mx_RoomAvatar_isSpaceRoom { + &.mx_BaseAvatar_image, .mx_BaseAvatar_image { + border-radius: 12px; + } } h1.mx_SpaceRoomView_preview_name { diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss index ff6910852c..a6b61d3ead 100644 --- a/res/css/views/beta/_BetaCard.scss +++ b/res/css/views/beta/_BetaCard.scss @@ -113,6 +113,7 @@ $dot-size: 12px; animation: mx_Beta_bluePulse 2s infinite; animation-iteration-count: 20; position: relative; + pointer-events: none; &::after { content: ""; diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index d1076205ad..16f607c95f 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -21,6 +21,17 @@ limitations under the License. .mx_SettingsTab_section { margin-bottom: 30px; + + > details { + > summary { + cursor: pointer; + color: $primary-content; + } + + & + .mx_SettingsFlag { + margin-top: 20px; + } + } } .mx_PreferencesUserSettingsTab_CommunityMigrator { diff --git a/res/img/betas/.gitkeep b/res/img/betas/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/res/img/betas/spaces.png b/res/img/betas/spaces.png deleted file mode 100644 index f4cfa90b4e..0000000000 Binary files a/res/img/betas/spaces.png and /dev/null differ diff --git a/src/components/structures/GroupFilterPanel.tsx b/src/components/structures/GroupFilterPanel.tsx index 3e7c6e9b17..b6d05efa87 100644 --- a/src/components/structures/GroupFilterPanel.tsx +++ b/src/components/structures/GroupFilterPanel.tsx @@ -146,19 +146,13 @@ class GroupFilterPanel extends React.Component; - } - let createButton = ( - { betaDot } - + className="mx_TagTile mx_TagTile_plus" + /> ); if (SettingsStore.getValue("feature_communities_v2_prototypes")) { diff --git a/src/components/structures/LegacyCommunityPreview.tsx b/src/components/structures/LegacyCommunityPreview.tsx new file mode 100644 index 0000000000..92aea8bb7d --- /dev/null +++ b/src/components/structures/LegacyCommunityPreview.tsx @@ -0,0 +1,116 @@ +/* +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, { useContext } from "react"; + +import MatrixClientContext from "../../contexts/MatrixClientContext"; +import { _t } from "../../languageHandler"; +import AccessibleButton from "../views/elements/AccessibleButton"; +import ErrorBoundary from "../views/elements/ErrorBoundary"; +import { IGroupSummary } from "../views/dialogs/CreateSpaceFromCommunityDialog"; +import { useAsyncMemo } from "../../hooks/useAsyncMemo"; +import Spinner from "../views/elements/Spinner"; +import GroupAvatar from "../views/avatars/GroupAvatar"; +import { linkifyElement } from "../../HtmlUtils"; +import defaultDispatcher from "../../dispatcher/dispatcher"; +import { Action } from "../../dispatcher/actions"; +import { UserTab } from "../views/dialogs/UserSettingsDialog"; +import MainSplit from './MainSplit'; + +interface IProps { + groupId: string; +} + +const onSwapClick = () => { + defaultDispatcher.dispatch({ + action: Action.ViewUserSettings, + initialTabId: UserTab.Preferences, + }); +}; + +// XXX: temporary community migration component, reuses SpaceRoomView & SpacePreview classes for simplicity +const LegacyCommunityPreview = ({ groupId }: IProps) => { + const cli = useContext(MatrixClientContext); + + const groupSummary = useAsyncMemo(() => cli.getGroupSummary(groupId), [cli, groupId]); + + if (!groupSummary) { + return
+ +
+ +
+
+
; + } + + let visibilitySection: JSX.Element; + if (groupSummary.profile.is_public) { + visibilitySection = + { _t("Public community") } + ; + } else { + visibilitySection = + { _t("Private community") } + ; + } + + return
+ + +
+ +

+ { groupSummary.profile.name } +

+
+ { visibilitySection } +
+
e && linkifyElement(e)}> + { groupSummary.profile.short_description } +
+
+ { groupSummary.user?.membership === "join" + ? _t("To view %(communityName)s, swap to communities in your preferences", { + communityName: groupSummary.profile.name, + }, { + a: sub => ( + { sub } + ), + }) + : _t("To join %(communityName)s, swap to communities in your preferences", { + communityName: groupSummary.profile.name, + }, { + a: sub => ( + { sub } + ), + }) + } +
+
+
+
+
; +}; + +export default LegacyCommunityPreview; diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index bbe0e42a0a..84e0b446f5 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -69,6 +69,7 @@ import classNames from 'classnames'; import GroupFilterPanel from './GroupFilterPanel'; import CustomRoomTagPanel from './CustomRoomTagPanel'; import { mediaFromMxc } from "../../customisations/Media"; +import LegacyCommunityPreview from "./LegacyCommunityPreview"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -629,11 +630,15 @@ class LoggedInView extends React.Component { pageElement = ; break; case PageTypes.GroupView: - pageElement = ; + if (SpaceStore.spacesEnabled) { + pageElement = ; + } else { + pageElement = ; + } break; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 280d56d3c0..2ab68998c3 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1800,11 +1800,6 @@ export default class MatrixChat extends React.PureComponent { subAction: params.action, }); } else if (screen.indexOf('group/') === 0) { - if (SpaceStore.spacesEnabled) { - dis.dispatch({ action: "view_home_page" }); - return; - } - const groupId = screen.substring(6); // TODO: Check valid group ID diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index dab18c4161..2239325f1b 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -25,7 +25,6 @@ import AccessibleButton from '../views/elements/AccessibleButton'; import MatrixClientContext from "../../contexts/MatrixClientContext"; import AutoHideScrollbar from "./AutoHideScrollbar"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import BetaCard from "../views/beta/BetaCard"; @replaceableComponent("structures.MyGroups") export default class MyGroups extends React.Component { @@ -138,7 +137,6 @@ export default class MyGroups extends React.Component { */ } -
{ contentHeader } { content } diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 3837d26564..4dfb1ddad8 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -156,10 +156,10 @@ const SpaceInfo = ({ space }) => {
; }; -const onBetaClick = () => { +const onPreferencesClick = () => { defaultDispatcher.dispatch({ action: Action.ViewUserSettings, - initialTabId: UserTab.Labs, + initialTabId: UserTab.Preferences, }); }; @@ -286,15 +286,11 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }: ISp if (!spacesEnabled) { footer =
{ myMembership === "join" - ? _t("To view %(spaceName)s, turn on the Spaces beta", { - spaceName: space.name, - }, { - a: sub => { sub }, + ? _t("To view this Space, hide communities in your preferences", {}, { + a: sub => { sub }, }) - : _t("To join %(spaceName)s, turn on the Spaces beta", { - spaceName: space.name, - }, { - a: sub => { sub }, + : _t("To join this Space, hide communities in your preferences", {}, { + a: sub => { sub }, }) }
; @@ -731,7 +727,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
- + { _t("This is an experimental feature. For now, " + "new users receiving an invite will have to open the invite on to actually join.", {}, { b: sub => { sub }, diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 2209537967..28708fd38a 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -28,7 +28,6 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent" import SettingsFlag from '../../../elements/SettingsFlag'; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import AccessibleButton from "../../../elements/AccessibleButton"; -import SpaceStore from "../../../../../stores/SpaceStore"; import GroupAvatar from "../../../avatars/GroupAvatar"; import dis from "../../../../../dispatcher/dispatcher"; import GroupActions from "../../../../../actions/GroupActions"; @@ -145,7 +144,7 @@ export default class PreferencesUserSettingsTab extends React.Component { - return ; + private renderGroup( + settingIds: string[], + level = SettingLevel.ACCOUNT, + includeDisabled = false, + ): React.ReactNodeArray { + if (!includeDisabled) { + settingIds = settingIds.filter(SettingsStore.isEnabled); + } + + return settingIds.map(i => { + return ; }); } @@ -334,10 +341,10 @@ export default class PreferencesUserSettingsTab extends React.Component - { SpaceStore.spacesEnabled &&
+
{ _t("Spaces") } - { this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS) } -
} + { this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS, SettingLevel.ACCOUNT, true) } +
{ _t("Communities") } @@ -349,7 +356,7 @@ export default class PreferencesUserSettingsTab extends React.Component{ _t("If a community isn't shown you may not have permission to convert it.") }

- { this.renderGroup(PreferencesUserSettingsTab.COMMUNITIES_SETTINGS) } + { this.renderGroup(PreferencesUserSettingsTab.COMMUNITIES_SETTINGS, SettingLevel.DEVICE) }
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index c09b26e45f..52f7786957 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -117,9 +117,7 @@ export const SpaceFeedbackPrompt = ({ onClick }: { onClick?: () => void }) => { "Your feedback will help inform the next versions."), rageshakeLabel: "spaces-feedback", rageshakeData: Object.fromEntries([ - "feature_spaces.all_rooms", - "feature_spaces.space_member_dms", - "feature_spaces.space_dm_badges", + "Spaces.allRoomsInHome", ].map(k => [k, SettingsStore.getValue(k)])), }); }} @@ -301,13 +299,13 @@ const SpaceCreateMenu = ({ onFinished }) => { />

- { _t("You can also create a Space from a community.", {}, { + { _t("You can also make Spaces from communities.", {}, { a: sub => { sub } , }) }
- { _t("To join an existing space you'll need an invite.") } + { _t("To join a space you'll need an invite.") }

diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index d223f5b6a6..67055d7418 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -151,12 +151,19 @@ const CreateSpaceButton = ({ } const onNewClick = menuDisplayed ? closeMenu : () => { + // persist that the user has interacted with this, use it to dismiss the beta dot + localStorage.setItem("mx_seenSpaces", "1"); if (!isPanelCollapsed) setPanelCollapsed(true); openMenu(); }; + let betaDot: JSX.Element; + if (!localStorage.getItem("mx_seenSpaces") && !SpaceStore.instance.spacePanelSpaces.length) { + betaDot =
; + } + return
  • + { betaDot } { contextMenu }
  • ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9c8bb513b9..6d1661fb09 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -799,15 +799,6 @@ "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Change notification settings": "Change notification settings", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators", - "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.", - "Spaces": "Spaces", - "Spaces are a new way to group rooms and people.": "Spaces are a new way to group rooms and people.", - "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.": "If you leave, %(brand)s will reload with Spaces disabled. Communities and custom tags will be visible again.", - "Beta available for web, desktop and Android. Thank you for trying the beta.": "Beta available for web, desktop and Android. Thank you for trying the beta.", - "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.": "%(brand)s will reload with Spaces enabled. Communities and custom tags will be hidden.", - "You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "You can leave the beta any time from settings or tapping on a beta badge, like the one above.", - "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.", - "Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.", "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Render LaTeX maths in messages": "Render LaTeX maths in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", @@ -883,6 +874,8 @@ "Show chat effects (animations when receiving e.g. confetti)": "Show chat effects (animations when receiving e.g. confetti)", "Show all rooms in Home": "Show all rooms in Home", "All rooms you're in will appear in Home.": "All rooms you're in will appear in Home.", + "Display Communities instead of Spaces": "Display Communities instead of Spaces", + "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading logs": "Uploading logs", @@ -1033,14 +1026,15 @@ "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.": "Spaces are a new way to group rooms and people.", "What kind of Space do you want to create?": "What kind of Space do you want to create?", "You can change this later.": "You can change this later.", "Public": "Public", "Open space for anyone, best for communities": "Open space for anyone, best for communities", "Private": "Private", "Invite only, best for yourself or teams": "Invite only, best for yourself or teams", - "You can also create a Space from a community.": "You can also create a Space from a community.", - "To join an existing space you'll need an invite.": "To join an existing space you'll need an invite.", + "You can also make Spaces from communities.": "You can also make Spaces from communities.", + "To join a space you'll need an invite.": "To join a space you'll need an invite.", "Go back": "Go back", "Your public space": "Your public space", "Your private space": "Your private space", @@ -1052,6 +1046,7 @@ "Show all rooms": "Show all rooms", "All rooms": "All rooms", "Options": "Options", + "Spaces": "Spaces", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", "Click to copy": "Click to copy", @@ -2788,6 +2783,10 @@ "Create a Group Chat": "Create a Group Chat", "Upgrade to %(hostSignupBrand)s": "Upgrade to %(hostSignupBrand)s", "Open dial pad": "Open dial pad", + "Public community": "Public community", + "Private community": "Private community", + "To view %(communityName)s, swap to communities in your preferences": "To view %(communityName)s, swap to communities in your preferences", + "To join %(communityName)s, swap to communities in your preferences": "To join %(communityName)s, swap to communities in your preferences", "Failed to reject invitation": "Failed to reject invitation", "Cannot create rooms in this community": "Cannot create rooms in this community", "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", @@ -2818,7 +2817,6 @@ "Error whilst fetching joined communities": "Error whilst fetching joined communities", "Create a new community": "Create a new community", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", - "Communities are changing to Spaces": "Communities are changing to Spaces", "You’re all caught up": "You’re all caught up", "You have no visible notifications.": "You have no visible notifications.", "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", @@ -2885,8 +2883,8 @@ "Search names and descriptions": "Search names and descriptions", "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", - "To join %(spaceName)s, turn on the Spaces beta": "To join %(spaceName)s, turn on the Spaces beta", + "To view this Space, hide communities in your preferences": "To view this Space, hide communities in your preferences", + "To join this Space, hide communities in your preferences": "To join this Space, hide communities in your preferences", "To view %(spaceName)s, you need an invite": "To view %(spaceName)s, you need an invite", "Created from ": "Created from ", "Welcome to ": "Welcome to ", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 6dbefd4b8e..9fba69a63b 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -16,9 +16,9 @@ limitations under the License. */ import { MatrixClient } from 'matrix-js-sdk/src/client'; -import React, { ReactNode } from "react"; +import { ReactNode } from "react"; -import { _t, _td } from '../languageHandler'; +import { _td } from '../languageHandler'; import { NotificationBodyEnabledController, NotificationsEnabledController, @@ -40,7 +40,6 @@ import { OrderedMultiController } from "./controllers/OrderedMultiController"; import { Layout } from "./Layout"; import ReducedMotionController from './controllers/ReducedMotionController'; import IncompatibleController from "./controllers/IncompatibleController"; -import SdkConfig from "../SdkConfig"; import PseudonymousAnalyticsController from './controllers/PseudonymousAnalyticsController'; import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController'; @@ -145,44 +144,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_FEATURE, default: false, }, - "feature_spaces": { - isFeature: true, - displayName: _td("Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. " + - "Requires compatible homeserver for some features."), - supportedLevels: LEVELS_FEATURE, - default: false, - controller: new ReloadOnChangeController(), - betaInfo: { - title: _td("Spaces"), - caption: _td("Spaces are a new way to group rooms and people."), - disclaimer: (enabled) => { - if (enabled) { - return <> -

    { _t("If you leave, %(brand)s will reload with Spaces disabled. " + - "Communities and custom tags will be visible again.", { - brand: SdkConfig.get().brand, - }) }

    -

    { _t("Beta available for web, desktop and Android. Thank you for trying the beta.") }

    - ; - } - - return <> -

    { _t("%(brand)s will reload with Spaces enabled. " + - "Communities and custom tags will be hidden.", { - brand: SdkConfig.get().brand, - }) }

    - { _t("You can leave the beta any time from settings or tapping on a beta badge, " + - "like the one above.") } -

    { _t("Beta available for web, desktop and Android. " + - "Some features may be unavailable on your homeserver.") }

    - ; - }, - image: require("../../res/img/betas/spaces.png"), - feedbackSubheading: _td("Your feedback will help make spaces better. " + - "The more detail you can go into, the better."), - feedbackLabel: "spaces-feedback", - }, - }, "feature_dnd": { isFeature: true, displayName: _td("Show options to enable 'Do not disturb' mode"), @@ -203,7 +164,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { ), supportedLevels: LEVELS_FEATURE, default: false, - controller: new IncompatibleController("feature_spaces"), + controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", false, false), }, "feature_pinning": { isFeature: true, @@ -232,7 +193,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Group & filter rooms by custom tags (refresh to apply changes)"), supportedLevels: LEVELS_FEATURE, default: false, - controller: new IncompatibleController("feature_spaces"), + controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", false, false), }, "feature_state_counters": { isFeature: true, @@ -780,6 +741,15 @@ export const SETTINGS: {[setting: string]: ISetting} = { description: _td("All rooms you're in will appear in Home."), supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: false, + controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", null), + }, + "showCommunitiesInsteadOfSpaces": { + displayName: _td("Display Communities instead of Spaces"), + description: _td("Temporarily show communities instead of Spaces for this session. " + + "Support for this will be removed in the near future. This will reload Element."), + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, + default: false, + controller: new ReloadOnChangeController(), }, [UIFeature.RoomHistorySettings]: { supportedLevels: LEVELS_UI_FEATURE, @@ -844,7 +814,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { [UIFeature.Communities]: { supportedLevels: LEVELS_UI_FEATURE, default: true, - controller: new IncompatibleController("feature_spaces"), + controller: new IncompatibleController("showCommunitiesInsteadOfSpaces", false, false), }, [UIFeature.AdvancedSettings]: { supportedLevels: LEVELS_UI_FEATURE, diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index c5b83cbcd0..9487feff5e 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -467,6 +467,10 @@ export default class SettingsStore { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); } + if (!SettingsStore.isEnabled(settingName)) { + return false; + } + // When non-beta features are specified in the config.json, we force them as enabled or disabled. if (SettingsStore.isFeature(settingName) && !SETTINGS[settingName]?.betaInfo) { const configVal = SettingsStore.getValueAt(SettingLevel.CONFIG, settingName, roomId, true, true); diff --git a/src/settings/controllers/IncompatibleController.ts b/src/settings/controllers/IncompatibleController.ts index c48ce0a60b..aa064219f9 100644 --- a/src/settings/controllers/IncompatibleController.ts +++ b/src/settings/controllers/IncompatibleController.ts @@ -24,7 +24,11 @@ import SettingsStore from "../SettingsStore"; * labs flags. */ export default class IncompatibleController extends SettingController { - public constructor(private settingName: string, private forcedValue = false) { + public constructor( + private settingName: string, + private forcedValue: any = false, + private incompatibleValue: any = true, + ) { super(); } @@ -34,13 +38,17 @@ export default class IncompatibleController extends SettingController { calculatedValue: any, calculatedAtLevel: SettingLevel, ): any { - if (this.incompatibleSettingEnabled) { + if (this.incompatibleSetting) { return this.forcedValue; } return null; // no override } - public get incompatibleSettingEnabled(): boolean { - return SettingsStore.getValue(this.settingName); + public get settingDisabled(): boolean { + return this.incompatibleSetting; + } + + public get incompatibleSetting(): boolean { + return SettingsStore.getValue(this.settingName) === this.incompatibleValue; } } diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 8381d9e429..f28d279d00 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -71,7 +71,7 @@ export interface ISuggestedRoom extends IHierarchyRoom { const MAX_SUGGESTED_ROOMS = 20; // This setting causes the page to reload and can be costly if read frequently, so read it here only -const spacesEnabled = SettingsStore.getValue("feature_spaces"); +const spacesEnabled = !SettingsStore.getValue("showCommunitiesInsteadOfSpaces"); const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "HOME_SPACE"}`; diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js index cee0fe5a47..760aee69a8 100644 --- a/test/components/views/rooms/RoomList-test.js +++ b/test/components/views/rooms/RoomList-test.js @@ -9,23 +9,16 @@ import sdk from '../../../skinned-sdk'; import dis from '../../../../src/dispatcher/dispatcher'; import DMRoomMap from '../../../../src/utils/DMRoomMap'; -import GroupStore from '../../../../src/stores/GroupStore'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk'; import { DefaultTagID } from "../../../../src/stores/room-list/models"; -import RoomListStore, { LISTS_UPDATE_EVENT, RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore"; +import RoomListStore, { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; } -function waitForRoomListStoreUpdate() { - return new Promise((resolve) => { - RoomListStore.instance.once(LISTS_UPDATE_EVENT, () => resolve()); - }); -} - describe('RoomList', () => { function createRoom(opts) { const room = new Room(generateRoomId(), MatrixClientPeg.get(), client.getUserId(), { @@ -239,73 +232,6 @@ describe('RoomList', () => { }); } - describe('when no tags are selected', () => { - itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); - }); - - describe('when tags are selected', () => { - function setupSelectedTag() { - // Simulate a complete sync BEFORE dispatching anything else - dis.dispatch({ - action: 'MatrixActions.sync', - prevState: null, - state: 'PREPARED', - matrixClient: client, - }, true); - - // Simulate joined groups being received - dis.dispatch({ - action: 'GroupActions.fetchJoinedGroups.success', - result: { - groups: ['+group:domain'], - }, - }, true); - - // Simulate receiving tag ordering account data - dis.dispatch({ - action: 'MatrixActions.accountData', - event_type: 'im.vector.web.tag_ordering', - event_content: { - tags: ['+group:domain'], - }, - }, true); - - // GroupStore is not flux, mock and notify - GroupStore.getGroupRooms = (groupId) => { - return [movingRoom]; - }; - GroupStore._notifyListeners(); - - // We also have to mock the client's getGroup function for the room list to filter it. - // It's not smart enough to tell the difference between a real group and a template though. - client.getGroup = (groupId) => { - return { groupId }; - }; - - // Select tag - dis.dispatch({ action: 'select_tag', tag: '+group:domain' }, true); - } - - beforeEach(() => { - setupSelectedTag(); - }); - - it('displays the correct rooms when the groups rooms are changed', async () => { - GroupStore.getGroupRooms = (groupId) => { - return [movingRoom, otherRoom]; - }; - GroupStore._notifyListeners(); - - await waitForRoomListStoreUpdate(); - - // XXX: Even though the store updated, it can take a bit before the update makes - // it to the components. This gives it plenty of time to figure out what to do. - await (new Promise(resolve => setTimeout(resolve, 500))); - - expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged); - }); - - itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); - }); + itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); }); diff --git a/test/end-to-end-tests/src/scenario.js b/test/end-to-end-tests/src/scenario.js index c44f209bf3..b6581d4930 100644 --- a/test/end-to-end-tests/src/scenario.js +++ b/test/end-to-end-tests/src/scenario.js @@ -43,6 +43,9 @@ module.exports = async function scenario(createSession, restCreator) { console.log("create REST users:"); const charlies = await createRestUsers(restCreator); await lazyLoadingScenarios(alice, bob, charlies); + // do spaces scenarios last as the rest of the tests may get confused by spaces + // XXX: disabled for now as fails in CI but succeeds locally + // await spacesScenarios(alice, bob); }; async function createRestUsers(restCreator) { diff --git a/test/end-to-end-tests/src/scenarios/spaces.js b/test/end-to-end-tests/src/scenarios/spaces.js new file mode 100644 index 0000000000..303db65593 --- /dev/null +++ b/test/end-to-end-tests/src/scenarios/spaces.js @@ -0,0 +1,32 @@ +/* +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. +*/ + +const { createSpace, inviteSpace } = require("../usecases/create-space"); + +module.exports = async function spacesScenarios(alice, bob) { + console.log(" creating a space for spaces scenarios:"); + + await alice.delay(1000); // wait for dialogs to close + await setupSpaceUsingAliceAndInviteBob(alice, bob); +}; + +const space = "Test Space"; + +async function setupSpaceUsingAliceAndInviteBob(alice, bob) { + await createSpace(alice, space); + await inviteSpace(alice, space, "@bob:localhost"); + await bob.query(`.mx_SpaceButton[aria-label="${space}"]`); // assert invite received +} diff --git a/test/end-to-end-tests/src/usecases/create-space.js b/test/end-to-end-tests/src/usecases/create-space.js new file mode 100644 index 0000000000..f1dbfa6b5a --- /dev/null +++ b/test/end-to-end-tests/src/usecases/create-space.js @@ -0,0 +1,80 @@ +/* +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. +*/ + +async function openSpaceCreateMenu(session) { + const spaceCreateButton = await session.query('.mx_SpaceButton_new'); + await spaceCreateButton.click(); +} + +async function createSpace(session, name, isPublic = false) { + session.log.step(`creates space "${name}"`); + + await openSpaceCreateMenu(session); + const className = isPublic ? ".mx_SpaceCreateMenuType_public" : ".mx_SpaceCreateMenuType_private"; + const visibilityButton = await session.query(className); + await visibilityButton.click(); + + const nameInput = await session.query('input[name="spaceName"]'); + await session.replaceInputText(nameInput, name); + + await session.delay(100); + + const createButton = await session.query('.mx_SpaceCreateMenu_wrapper .mx_AccessibleButton_kind_primary'); + await createButton.click(); + + if (!isPublic) { + const justMeButton = await session.query('.mx_SpaceRoomView_privateScope_justMeButton'); + await justMeButton.click(); + const continueButton = await session.query('.mx_AddExistingToSpace_footer .mx_AccessibleButton_kind_primary'); + await continueButton.click(); + } else { + for (let i = 0; i < 2; i++) { + const continueButton = await session.query('.mx_SpaceRoomView_buttons .mx_AccessibleButton_kind_primary'); + await continueButton.click(); + } + } + + session.log.done(); +} + +async function inviteSpace(session, spaceName, userId) { + session.log.step(`invites "${userId}" to space "${spaceName}"`); + + const spaceButton = await session.query(`.mx_SpaceButton[aria-label="${spaceName}"]`); + await spaceButton.click({ + button: 'right', + }); + + const inviteButton = await session.query('[aria-label="Invite people"]'); + await inviteButton.click(); + + try { + const button = await session.query('.mx_SpacePublicShare_inviteButton'); + await button.click(); + } catch (e) { + // ignore + } + + const inviteTextArea = await session.query(".mx_InviteDialog_editor input"); + await inviteTextArea.type(userId); + const selectUserItem = await session.query(".mx_InviteDialog_roomTile"); + await selectUserItem.click(); + const confirmButton = await session.query(".mx_InviteDialog_goButton"); + await confirmButton.click(); + session.log.done(); +} + +module.exports = { openSpaceCreateMenu, createSpace, inviteSpace }; diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js index 01431197a7..83d6fd79a8 100644 --- a/test/end-to-end-tests/src/usecases/room-settings.js +++ b/test/end-to-end-tests/src/usecases/room-settings.js @@ -161,8 +161,8 @@ async function changeRoomSettings(session, settings) { if (settings.visibility) { session.log.step(`sets visibility to ${settings.visibility}`); const radios = await session.queryAll(".mx_RoomSettingsDialog label"); - assert.equal(radios.length, 6); - const [inviteOnlyRoom, publicRoom] = radios; + assert.equal(radios.length, 7); + const [inviteOnlyRoom,, publicRoom] = radios; if (settings.visibility === "invite_only") { await inviteOnlyRoom.click(); diff --git a/test/stores/SpaceStore-setup.ts b/test/stores/SpaceStore-setup.ts deleted file mode 100644 index 78418d45cc..0000000000 --- a/test/stores/SpaceStore-setup.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* -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. -*/ - -// This needs to be executed before the SpaceStore gets imported but due to ES6 import hoisting we have to do this here. -// SpaceStore reads the SettingsStore which needs the localStorage values set at init time. - -localStorage.setItem("mx_labs_feature_feature_spaces", "true"); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 698bd01370..e7ca727e28 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -18,7 +18,6 @@ import { EventEmitter } from "events"; import { EventType } from "matrix-js-sdk/src/@types/event"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import "./SpaceStore-setup"; // enable space lab import "../skinned-sdk"; // Must be first for skinning to work import SpaceStore, { UPDATE_HOME_BEHAVIOUR, diff --git a/test/stores/room-list/SpaceWatcher-test.ts b/test/stores/room-list/SpaceWatcher-test.ts index 474c279fdd..cb2394349a 100644 --- a/test/stores/room-list/SpaceWatcher-test.ts +++ b/test/stores/room-list/SpaceWatcher-test.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import "../SpaceStore-setup"; // enable space lab import "../../skinned-sdk"; // Must be first for skinning to work import { SpaceWatcher } from "../../../src/stores/room-list/SpaceWatcher"; import type { RoomListStoreClass } from "../../../src/stores/room-list/RoomListStore";