Add new in the spaces beta toast & explanatory modal

This commit is contained in:
Michael Telatynski 2021-07-02 16:07:17 +01:00
parent 912e192dc6
commit 89949bd884
8 changed files with 119 additions and 49 deletions

View file

@ -307,7 +307,6 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
}; };
const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => { const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
const cli = useContext(MatrixClientContext);
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
let contextMenu; let contextMenu;
@ -330,7 +329,7 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
e.stopPropagation(); e.stopPropagation();
closeMenu(); closeMenu();
if (await showCreateNewRoom(cli, space)) { if (await showCreateNewRoom(space)) {
onNewRoomAdded(); onNewRoomAdded();
} }
}} }}
@ -343,7 +342,7 @@ const SpaceLandingAddButton = ({ space, onNewRoomAdded }) => {
e.stopPropagation(); e.stopPropagation();
closeMenu(); closeMenu();
const [added] = await showAddExistingRooms(cli, space); const [added] = await showAddExistingRooms(space);
if (added) { if (added) {
onNewRoomAdded(); onNewRoomAdded();
} }
@ -397,11 +396,11 @@ const SpaceLanding = ({ space }) => {
} }
let settingsButton; let settingsButton;
if (shouldShowSpaceSettings(cli, space)) { if (shouldShowSpaceSettings(space)) {
settingsButton = <AccessibleTooltipButton settingsButton = <AccessibleTooltipButton
className="mx_SpaceRoomView_landing_settingsButton" className="mx_SpaceRoomView_landing_settingsButton"
onClick={() => { onClick={() => {
showSpaceSettings(cli, space); showSpaceSettings(space);
}} }}
title={_t("Settings")} title={_t("Settings")}
/>; />;

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, { ReactNode, useContext, useMemo, useState } from "react"; import React, { ReactNode, useContext, useMemo, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { IDialogProps } from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
@ -44,9 +43,8 @@ import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar"; import BaseAvatar from "../avatars/BaseAvatar";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
matrixClient: MatrixClient;
space: Room; space: Room;
onCreateRoomClick(cli: MatrixClient, space: Room): void; onCreateRoomClick(space: Room): void;
} }
const Entry = ({ room, checked, onChange }) => { const Entry = ({ room, checked, onChange }) => {
@ -295,7 +293,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
</div>; </div>;
}; };
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onFinished }) => {
const [selectedSpace, setSelectedSpace] = useState(space); const [selectedSpace, setSelectedSpace] = useState(space);
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
@ -344,13 +342,13 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
onFinished={onFinished} onFinished={onFinished}
fixedWidth={false} fixedWidth={false}
> >
<MatrixClientContext.Provider value={cli}> <MatrixClientContext.Provider value={space.client}>
<AddExistingToSpace <AddExistingToSpace
space={space} space={space}
onFinished={onFinished} onFinished={onFinished}
footerPrompt={<> footerPrompt={<>
<div>{ _t("Want to add a new room instead?") }</div> <div>{ _t("Want to add a new room instead?") }</div>
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link"> <AccessibleButton onClick={() => onCreateRoomClick(space)} kind="link">
{ _t("Create a new room") } { _t("Create a new room") }
</AccessibleButton> </AccessibleButton>
</>} </>}

View file

@ -1,7 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd.
Copyright 2019 Bastian Masanek, Noxware IT <matrix@noxware.de> Copyright 2019 Bastian Masanek, Noxware IT <matrix@noxware.de>
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -16,31 +15,31 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode, KeyboardEvent } from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from "classnames"; import classNames from "classnames";
export default class InfoDialog extends React.Component { import { _t } from '../../../languageHandler';
static propTypes = { import * as sdk from '../../../index';
className: PropTypes.string, import { IDialogProps } from "./IDialogProps";
title: PropTypes.string,
description: PropTypes.node,
button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
onFinished: PropTypes.func,
hasCloseButton: PropTypes.bool,
onKeyDown: PropTypes.func,
fixedWidth: PropTypes.bool,
};
interface IProps extends IDialogProps {
title?: string;
description?: ReactNode;
className?: string;
button?: boolean | string;
hasCloseButton?: boolean;
fixedWidth?: boolean;
onKeyDown?(event: KeyboardEvent): void;
}
export default class InfoDialog extends React.Component<IProps> {
static defaultProps = { static defaultProps = {
title: '', title: '',
description: '', description: '',
hasCloseButton: false, hasCloseButton: false,
}; };
onFinished = () => { private onFinished = () => {
this.props.onFinished(); this.props.onFinished();
}; };

View file

@ -140,7 +140,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onFinished(); onFinished();
showCreateNewRoom(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); showCreateNewRoom(SpaceStore.instance.activeSpace);
}} }}
disabled={!canAddRooms} disabled={!canAddRooms}
tooltip={canAddRooms ? undefined tooltip={canAddRooms ? undefined
@ -153,7 +153,7 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onFinished(); onFinished();
showAddExistingRooms(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); showAddExistingRooms(SpaceStore.instance.activeSpace);
}} }}
disabled={!canAddRooms} disabled={!canAddRooms}
tooltip={canAddRooms ? undefined tooltip={canAddRooms ? undefined

View file

@ -203,7 +203,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
showSpaceSettings(this.context, this.props.space); showSpaceSettings(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu this.setState({ contextMenuPosition: null }); // also close the menu
}; };
@ -222,7 +222,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
showCreateNewRoom(this.context, this.props.space); showCreateNewRoom(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu this.setState({ contextMenuPosition: null }); // also close the menu
}; };
@ -230,7 +230,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
showAddExistingRooms(this.context, this.props.space); showAddExistingRooms(this.props.space);
this.setState({ contextMenuPosition: null }); // also close the menu this.setState({ contextMenuPosition: null }); // also close the menu
}; };
@ -285,7 +285,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
let settingsOption; let settingsOption;
let leaveSection; let leaveSection;
if (shouldShowSpaceSettings(this.context, this.props.space)) { if (shouldShowSpaceSettings(this.props.space)) {
settingsOption = ( settingsOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconSettings" iconClassName="mx_SpacePanel_iconSettings"

View file

@ -774,6 +774,16 @@
"The person who invited you already left the room.": "The person who invited you already left the room.", "The person who invited you already left the room.": "The person who invited you already left the room.",
"The person who invited you already left the room, or their server is offline.": "The person who invited you already left the room, or their server is offline.", "The person who invited you already left the room, or their server is offline.": "The person who invited you already left the room, or their server is offline.",
"Failed to join room": "Failed to join room", "Failed to join room": "Failed to join room",
"New in the Spaces beta": "New in the Spaces beta",
"Help people in spaces to find and join private rooms": "Help people in spaces to find and join private rooms",
"Learn more": "Learn more",
"Help space members find private rooms": "Help space members find private rooms",
"To help space members find and join a private room, go to that room's Security & Privacy settings.": "To help space members find and join a private room, go to that room's Security & Privacy settings.",
"General": "General",
"Security & Privacy": "Security & Privacy",
"Roles & Permissions": "Roles & Permissions",
"This make it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.": "This make it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.",
"Skip": "Skip",
"You joined the call": "You joined the call", "You joined the call": "You joined the call",
"%(senderName)s joined the call": "%(senderName)s joined the call", "%(senderName)s joined the call": "%(senderName)s joined the call",
"Call in progress": "Call in progress", "Call in progress": "Call in progress",
@ -1040,7 +1050,6 @@
"Invite people": "Invite people", "Invite people": "Invite people",
"Invite with email or username": "Invite with email or username", "Invite with email or username": "Invite with email or username",
"Failed to save space settings.": "Failed to save space settings.", "Failed to save space settings.": "Failed to save space settings.",
"General": "General",
"Edit settings relating to your space.": "Edit settings relating to your space.", "Edit settings relating to your space.": "Edit settings relating to your space.",
"Saving...": "Saving...", "Saving...": "Saving...",
"Save Changes": "Save Changes", "Save Changes": "Save Changes",
@ -1430,7 +1439,6 @@
"Muted Users": "Muted Users", "Muted Users": "Muted Users",
"Banned users": "Banned users", "Banned users": "Banned users",
"Send %(eventType)s events": "Send %(eventType)s events", "Send %(eventType)s events": "Send %(eventType)s events",
"Roles & Permissions": "Roles & Permissions",
"Permissions": "Permissions", "Permissions": "Permissions",
"Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room", "Select the roles required to change various parts of the room": "Select the roles required to change various parts of the room",
"Enable encryption?": "Enable encryption?", "Enable encryption?": "Enable encryption?",
@ -1456,7 +1464,6 @@
"Members only (since they joined)": "Members only (since they joined)", "Members only (since they joined)": "Members only (since they joined)",
"People with supported clients will be able to join the room without having a registered account.": "People with supported clients will be able to join the room without having a registered account.", "People with supported clients will be able to join the room without having a registered account.": "People with supported clients will be able to join the room without having a registered account.",
"Who can read history?": "Who can read history?", "Who can read history?": "Who can read history?",
"Security & Privacy": "Security & Privacy",
"Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.", "Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.",
"Encrypted": "Encrypted", "Encrypted": "Encrypted",
"Access": "Access", "Access": "Access",
@ -2147,7 +2154,6 @@
"People you know on %(brand)s": "People you know on %(brand)s", "People you know on %(brand)s": "People you know on %(brand)s",
"Hide": "Hide", "Hide": "Hide",
"Show": "Show", "Show": "Show",
"Skip": "Skip",
"Send %(count)s invites|other": "Send %(count)s invites", "Send %(count)s invites|other": "Send %(count)s invites",
"Send %(count)s invites|one": "Send %(count)s invite", "Send %(count)s invites|one": "Send %(count)s invite",
"Invite people to join %(communityName)s": "Invite people to join %(communityName)s", "Invite people to join %(communityName)s": "Invite people to join %(communityName)s",
@ -2422,7 +2428,6 @@
"We call the places where you can host your account homeservers.": "We call the places where you can host your account homeservers.", "We call the places where you can host your account homeservers.": "We call the places where you can host your account homeservers.",
"Other homeserver": "Other homeserver", "Other homeserver": "Other homeserver",
"Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.", "Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.",
"Learn more": "Learn more",
"About homeservers": "About homeservers", "About homeservers": "About homeservers",
"Reset event store?": "Reset event store?", "Reset event store?": "Reset event store?",
"You most likely do not want to reset your event index store": "You most likely do not want to reset your event index store", "You most likely do not want to reset your event index store": "You most likely do not want to reset your event index store",

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react";
import { ListIteratee, Many, sortBy, throttle } from "lodash"; import { ListIteratee, Many, sortBy, throttle } from "lodash";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
@ -38,6 +39,13 @@ import { arrayHasDiff } from "../utils/arrays";
import { objectDiff } from "../utils/objects"; import { objectDiff } from "../utils/objects";
import { arrayHasOrderChange } from "../utils/arrays"; import { arrayHasOrderChange } from "../utils/arrays";
import { reorderLexicographically } from "../utils/stringOrderField"; import { reorderLexicographically } from "../utils/stringOrderField";
import { shouldShowSpaceSettings } from "../utils/space";
import ToastStore from "./ToastStore";
import { _t } from "../languageHandler";
import GenericToast from "../components/views/toasts/GenericToast";
import Modal from "../Modal";
import InfoDialog from "../components/views/dialogs/InfoDialog";
import { JoinRule } from "../../../matrix-js-sdk/src/@types/partials";
type SpaceKey = string | symbol; type SpaceKey = string | symbol;
@ -173,6 +181,68 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY); window.localStorage.removeItem(ACTIVE_SPACE_LS_KEY);
} }
// New in Spaces beta toast for Restricted Join Rule
(async () => {
const lsKey = "mx_SpaceBeta_restrictedJoinRuleToastSeen";
if (contextSwitch && space?.getJoinRule() === JoinRule.Invite && shouldShowSpaceSettings(space) &&
space.getJoinedMemberCount() > 1 && !localStorage.getItem(lsKey) /*&&
(await this.matrixClient.getCapabilities())
?.["m.room_versions"]?.["org.matrix.msc3244.room_capabilities"]?.["restricted"]?.preferred*/
) {
const toastKey = "restrictedjoinrule";
ToastStore.sharedInstance().addOrReplaceToast({
key: toastKey,
title: _t("New in the Spaces beta"),
props: {
description: _t("Help people in spaces to find and join private rooms"),
acceptLabel: _t("Learn more"),
onAccept: () => {
localStorage.setItem(lsKey, "true");
ToastStore.sharedInstance().dismissToast(toastKey);
Modal.createTrackedDialog("New in the Spaces beta", "restricted join rule", InfoDialog, {
title: _t("Help space members find private rooms"),
description: <>
<p>{ _t("To help space members find and join a private room, " +
"go to that room's Security & Privacy settings.") }</p>
{ /* Reuses classes from TabbedView for simplicity, non-interactive */ }
<div style={{ width: "190px" }}>
<div className="mx_TabbedView_tabLabel">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_settingsIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("General") }</span>
</div>
<div className="mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_securityIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("Security & Privacy") }</span>
</div>
<div className="mx_TabbedView_tabLabel">
<span className="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_rolesIcon" />
<span className="mx_TabbedView_tabLabel_text">{ _t("Roles & Permissions") }</span>
</div>
</div>
<p>{ _t("This make it easy for rooms to stay private to a space, " +
"while letting people in the space find and join them. " +
"All new rooms in a space will have this option available.")}</p>
</>,
button: _t("OK"),
hasCloseButton: false,
fixedWidth: true,
});
},
rejectLabel: _t("Skip"),
onReject: () => {
localStorage.setItem(lsKey, "true");
ToastStore.sharedInstance().dismissToast(toastKey);
},
},
component: GenericToast,
priority: 35,
});
}
})().then();
if (space) { if (space) {
const suggestedRooms = await this.fetchSuggestedRooms(space); const suggestedRooms = await this.fetchSuggestedRooms(space);
if (this._activeSpace === space) { if (this._activeSpace === space) {

View file

@ -16,10 +16,9 @@ limitations under the License.
import React from "react"; import React from "react";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { calculateRoomVia } from "../utils/permalinks/Permalinks"; import { calculateRoomVia } from "./permalinks/Permalinks";
import Modal from "../Modal"; import Modal from "../Modal";
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog"; import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog"; import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
@ -30,8 +29,8 @@ import SpacePublicShare from "../components/views/spaces/SpacePublicShare";
import InfoDialog from "../components/views/dialogs/InfoDialog"; import InfoDialog from "../components/views/dialogs/InfoDialog";
import { showRoomInviteDialog } from "../RoomInvite"; import { showRoomInviteDialog } from "../RoomInvite";
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { export const shouldShowSpaceSettings = (space: Room) => {
const userId = cli.getUserId(); const userId = space.client.getUserId();
return space.getMyMembership() === "join" return space.getMyMembership() === "join"
&& (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId) && (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId)
|| space.currentState.maySendStateEvent(EventType.RoomName, userId) || space.currentState.maySendStateEvent(EventType.RoomName, userId)
@ -48,20 +47,20 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
state_key: room.roomId, state_key: room.roomId,
}); });
export const showSpaceSettings = (cli: MatrixClient, space: Room) => { export const showSpaceSettings = (space: Room) => {
Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, { Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
matrixClient: cli, matrixClient: space.client,
space, space,
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
}; };
export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => { export const showAddExistingRooms = async (space: Room) => {
return Modal.createTrackedDialog( return Modal.createTrackedDialog(
"Space Landing", "Space Landing",
"Add Existing", "Add Existing",
AddExistingToSpaceDialog, AddExistingToSpaceDialog,
{ {
matrixClient: cli, matrixClient: space.client,
onCreateRoomClick: showCreateNewRoom, onCreateRoomClick: showCreateNewRoom,
space, space,
}, },
@ -69,7 +68,7 @@ export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
).finished; ).finished;
}; };
export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => { export const showCreateNewRoom = async (space: Room) => {
const modal = Modal.createTrackedDialog<[boolean, IOpts]>( const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
"Space Landing", "Space Landing",
"Create Room", "Create Room",