Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit c6c374c

Browse files
committed
Add customisation point for visibility of invites and room creation
Fixes element-hq/element-web#19331
1 parent fe79a95 commit c6c374c

File tree

11 files changed

+146
-43
lines changed

11 files changed

+146
-43
lines changed

src/SlashCommands.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { Action } from "./dispatcher/actions";
4444
import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
4545
import SdkConfig from "./SdkConfig";
4646
import SettingsStore from "./settings/SettingsStore";
47-
import { UIFeature } from "./settings/UIFeature";
47+
import { UIComponent, UIFeature } from "./settings/UIFeature";
4848
import { CHAT_EFFECTS } from "./effects";
4949
import CallHandler from "./CallHandler";
5050
import { guessAndSetDMRoom } from "./Rooms";
@@ -56,6 +56,7 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
5656
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
5757

5858
import { logger } from "matrix-js-sdk/src/logger";
59+
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
5960

6061
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
6162
interface HTMLInputEvent extends Event {
@@ -403,6 +404,7 @@ export const Commands = [
403404
command: 'invite',
404405
args: '<user-id> [<reason>]',
405406
description: _td('Invites user with given id to current room'),
407+
isEnabled: () => shouldShowComponent(UIComponent.InviteUsers),
406408
runFn: function(roomId, args) {
407409
if (args) {
408410
const [address, reason] = args.split(/\s+(.+)/);

src/components/structures/SpaceRoomView.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ import GroupAvatar from "../views/avatars/GroupAvatar";
8181
import { useDispatcher } from "../../hooks/useDispatcher";
8282

8383
import { logger } from "matrix-js-sdk/src/logger";
84+
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
85+
import { UIComponent } from "../../settings/UIFeature";
8486

8587
interface IProps {
8688
space: Room;
@@ -411,7 +413,7 @@ const SpaceLanding = ({ space }) => {
411413
const userId = cli.getUserId();
412414

413415
let inviteButton;
414-
if (myMembership === "join" && space.canInvite(userId)) {
416+
if (myMembership === "join" && space.canInvite(userId) && shouldShowComponent(UIComponent.InviteUsers)) {
415417
inviteButton = (
416418
<AccessibleButton
417419
kind="primary"

src/components/views/right_panel/UserInfo.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
7272
import SpaceStore from "../../../stores/SpaceStore";
7373

7474
import { logger } from "matrix-js-sdk/src/logger";
75+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
76+
import { UIComponent } from "../../../settings/UIFeature";
7577

7678
export interface IDevice {
7779
deviceId: string;
@@ -393,7 +395,7 @@ const UserOptionsSection: React.FC<{
393395
);
394396
}
395397

396-
if (canInvite && (!member || !member.membership || member.membership === 'leave')) {
398+
if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) {
397399
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
398400
const onInviteUserButton = async () => {
399401
try {

src/components/views/rooms/MemberList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ import MemberTile from "./MemberTile";
4444
import BaseAvatar from '../avatars/BaseAvatar';
4545
import { throttle } from 'lodash';
4646
import SpaceStore from "../../../stores/SpaceStore";
47+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
48+
import { UIComponent } from "../../../settings/UIFeature";
4749

4850
const getSearchQueryLSKey = (roomId: string) => `mx_MemberList_searchQuarry_${roomId}`;
4951

@@ -530,7 +532,7 @@ export default class MemberList extends React.Component<IProps, IState> {
530532
const room = cli.getRoom(this.props.roomId);
531533
let inviteButton;
532534

533-
if (room && room.getMyMembership() === 'join') {
535+
if (room?.getMyMembership() === 'join' && shouldShowComponent(UIComponent.InviteUsers)) {
534536
let inviteButtonText = _t("Invite to this room");
535537
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
536538
if (chat && chat.roomId === this.props.roomId) {

src/components/views/rooms/NewRoomIntro.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,17 @@ import AccessibleButton from "../elements/AccessibleButton";
2828
import MiniAvatarUploader, { AVATAR_SIZE } from "../elements/MiniAvatarUploader";
2929
import RoomAvatar from "../avatars/RoomAvatar";
3030
import defaultDispatcher from "../../../dispatcher/dispatcher";
31+
import dis from "../../../dispatcher/dispatcher";
3132
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
3233
import { Action } from "../../../dispatcher/actions";
33-
import dis from "../../../dispatcher/dispatcher";
3434
import SpaceStore from "../../../stores/SpaceStore";
3535
import { showSpaceInvite } from "../../../utils/space";
3636
import { privateShouldBeEncrypted } from "../../../createRoom";
3737
import EventTileBubble from "../messages/EventTileBubble";
3838
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
3939
import { MatrixClientPeg } from "../../../MatrixClientPeg";
40+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
41+
import { UIComponent } from "../../../settings/UIFeature";
4042

4143
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
4244
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
@@ -150,7 +152,7 @@ const NewRoomIntro = () => {
150152
{ _t("Invite to just this room") }
151153
</AccessibleButton> }
152154
</div>;
153-
} else if (room.canInvite(cli.getUserId())) {
155+
} else if (room.canInvite(cli.getUserId()) && shouldShowComponent(UIComponent.InviteUsers)) {
154156
buttons = <div className="mx_NewRoomIntro_buttons">
155157
<AccessibleButton
156158
className="mx_NewRoomIntro_inviteButton"

src/components/views/rooms/RoomList.tsx

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../
4949
import { replaceableComponent } from "../../../utils/replaceableComponent";
5050
import RoomAvatar from "../avatars/RoomAvatar";
5151
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
52+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
53+
import { UIComponent } from "../../../settings/UIFeature";
5254

5355
interface IProps {
5456
onKeyDown: (ev: React.KeyboardEvent) => void;
@@ -133,32 +135,38 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
133135
MatrixClientPeg.get().getUserId());
134136

135137
return <IconizedContextMenuOptionList first>
136-
<IconizedContextMenuOption
137-
label={_t("Create new room")}
138-
iconClassName="mx_RoomList_iconPlus"
139-
onClick={(e) => {
140-
e.preventDefault();
141-
e.stopPropagation();
142-
onFinished();
143-
showCreateNewRoom(SpaceStore.instance.activeSpace);
144-
}}
145-
disabled={!canAddRooms}
146-
tooltip={canAddRooms ? undefined
147-
: _t("You do not have permissions to create new rooms in this space")}
148-
/>
149-
<IconizedContextMenuOption
150-
label={_t("Add existing room")}
151-
iconClassName="mx_RoomList_iconHash"
152-
onClick={(e) => {
153-
e.preventDefault();
154-
e.stopPropagation();
155-
onFinished();
156-
showAddExistingRooms(SpaceStore.instance.activeSpace);
157-
}}
158-
disabled={!canAddRooms}
159-
tooltip={canAddRooms ? undefined
160-
: _t("You do not have permissions to add rooms to this space")}
161-
/>
138+
{
139+
shouldShowComponent(UIComponent.CreateRooms)
140+
? (<>
141+
<IconizedContextMenuOption
142+
label={_t("Create new room")}
143+
iconClassName="mx_RoomList_iconPlus"
144+
onClick={(e) => {
145+
e.preventDefault();
146+
e.stopPropagation();
147+
onFinished();
148+
showCreateNewRoom(SpaceStore.instance.activeSpace);
149+
}}
150+
disabled={!canAddRooms}
151+
tooltip={canAddRooms ? undefined
152+
: _t("You do not have permissions to create new rooms in this space")}
153+
/>
154+
<IconizedContextMenuOption
155+
label={_t("Add existing room")}
156+
iconClassName="mx_RoomList_iconHash"
157+
onClick={(e) => {
158+
e.preventDefault();
159+
e.stopPropagation();
160+
onFinished();
161+
showAddExistingRooms(SpaceStore.instance.activeSpace);
162+
}}
163+
disabled={!canAddRooms}
164+
tooltip={canAddRooms ? undefined
165+
: _t("You do not have permissions to add rooms to this space")}
166+
/>
167+
</>)
168+
: null
169+
}
162170
<IconizedContextMenuOption
163171
label={_t("Explore rooms")}
164172
iconClassName="mx_RoomList_iconBrowse"

src/components/views/rooms/RoomSublist.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ import { ListNotificationState } from "../../../stores/notifications/ListNotific
5555
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
5656
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
5757
import { replaceableComponent } from "../../../utils/replaceableComponent";
58+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
59+
import { UIComponent } from "../../../settings/UIFeature";
5860

5961
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
6062
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
@@ -675,7 +677,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
675677
);
676678

677679
let addRoomButton = null;
678-
if (!!this.props.onAddRoom) {
680+
if (!!this.props.onAddRoom && shouldShowComponent(UIComponent.CreateRooms)) {
679681
addRoomButton = (
680682
<AccessibleTooltipButton
681683
tabIndex={tabIndex}
@@ -687,6 +689,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
687689
/>
688690
);
689691
} else if (this.props.addRoomContextMenu) {
692+
// We assume that shouldShowComponent() is checked by the context menu itself.
690693
addRoomButton = (
691694
<ContextMenuTooltipButton
692695
tabIndex={tabIndex}

src/components/views/spaces/SpacePublicShare.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { copyPlaintext } from "../../../utils/strings";
2424
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
2525
import { showRoomInviteDialog } from "../../../RoomInvite";
2626
import { MatrixClientPeg } from "../../../MatrixClientPeg";
27+
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
28+
import { UIComponent } from "../../../settings/UIFeature";
2729

2830
interface IProps {
2931
space: Room;
@@ -51,16 +53,18 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
5153
<h3>{ _t("Share invite link") }</h3>
5254
<span>{ copiedText }</span>
5355
</AccessibleButton>
54-
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
55-
className="mx_SpacePublicShare_inviteButton"
56-
onClick={() => {
57-
if (onFinished) onFinished();
58-
showRoomInviteDialog(space.roomId);
59-
}}
60-
>
61-
<h3>{ _t("Invite people") }</h3>
62-
<span>{ _t("Invite with email or username") }</span>
63-
</AccessibleButton> : null }
56+
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) && shouldShowComponent(UIComponent.InviteUsers)
57+
? <AccessibleButton
58+
className="mx_SpacePublicShare_inviteButton"
59+
onClick={() => {
60+
if (onFinished) onFinished();
61+
showRoomInviteDialog(space.roomId);
62+
}}
63+
>
64+
<h3>{ _t("Invite people") }</h3>
65+
<span>{ _t("Invite with email or username") }</span>
66+
</AccessibleButton>
67+
: null }
6468
</div>;
6569
};
6670

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2021 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Dev note: this customisation point is heavily inspired by UIFeature flags, though
18+
// with an intention of being used for more complex switching on whether or not a feature
19+
// should be shown.
20+
21+
// Populate this class with the details of your customisations when copying it.
22+
23+
import { UIComponent } from "../settings/UIFeature";
24+
25+
/**
26+
* Determines whether or not the active MatrixClient user should be able to use
27+
* the given UI component. If shown, the user might still not be able to use the
28+
* component depending on their contextual permissions. For example, invite options
29+
* might be shown to the user but they won't have permission to invite users to
30+
* the current room: the button will appear disabled.
31+
* @param {UIComponent} component The component to check visibility for.
32+
* @returns {boolean} True (default) if the user is able to see the component, false
33+
* otherwise.
34+
*/
35+
function shouldShowComponent(component: UIComponent): boolean {
36+
return true; // default to visible
37+
}
38+
39+
// This interface summarises all available customisation points and also marks
40+
// them all as optional. This allows customisers to only define and export the
41+
// customisations they need while still maintaining type safety.
42+
export interface IComponentVisibilityCustomisations {
43+
shouldShowComponent?: typeof shouldShowComponent;
44+
}
45+
46+
// A real customisation module will define and export one or more of the
47+
// customisation points that make up the interface above.
48+
export const ComponentVisibilityCustomisations: IComponentVisibilityCustomisations = {
49+
// while we don't specify the functions here, their defaults are described
50+
// in their pseudo-implementations above.
51+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright 2021 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { UIComponent } from "../../settings/UIFeature";
18+
import { ComponentVisibilityCustomisations } from "../ComponentVisibility";
19+
20+
export function shouldShowComponent(component: UIComponent): boolean {
21+
return ComponentVisibilityCustomisations.shouldShowComponent?.(component) ?? true;
22+
}

src/settings/UIFeature.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ export enum UIFeature {
3333
AdvancedSettings = "UIFeature.advancedSettings",
3434
RoomHistorySettings = "UIFeature.roomHistorySettings",
3535
}
36+
37+
export enum UIComponent {
38+
InviteUsers = "UIComponent.sendInvites",
39+
CreateRooms = "UIComponent.roomCreation",
40+
}

0 commit comments

Comments
 (0)