mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 19:56:47 +03:00
Merge pull request #6922 from matrix-org/travis/hide-buttons-customisation
Add customisation point for visibility of invites and room creation
This commit is contained in:
commit
3643359c15
11 changed files with 146 additions and 43 deletions
|
@ -44,7 +44,7 @@ import { Action } from "./dispatcher/actions";
|
||||||
import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
|
import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { UIFeature } from "./settings/UIFeature";
|
import { UIComponent, UIFeature } from "./settings/UIFeature";
|
||||||
import { CHAT_EFFECTS } from "./effects";
|
import { CHAT_EFFECTS } from "./effects";
|
||||||
import CallHandler from "./CallHandler";
|
import CallHandler from "./CallHandler";
|
||||||
import { guessAndSetDMRoom } from "./Rooms";
|
import { guessAndSetDMRoom } from "./Rooms";
|
||||||
|
@ -56,6 +56,7 @@ import InfoDialog from "./components/views/dialogs/InfoDialog";
|
||||||
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
|
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
|
||||||
|
|
||||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
interface HTMLInputEvent extends Event {
|
interface HTMLInputEvent extends Event {
|
||||||
|
@ -403,6 +404,7 @@ export const Commands = [
|
||||||
command: 'invite',
|
command: 'invite',
|
||||||
args: '<user-id> [<reason>]',
|
args: '<user-id> [<reason>]',
|
||||||
description: _td('Invites user with given id to current room'),
|
description: _td('Invites user with given id to current room'),
|
||||||
|
isEnabled: () => shouldShowComponent(UIComponent.InviteUsers),
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
const [address, reason] = args.split(/\s+(.+)/);
|
const [address, reason] = args.split(/\s+(.+)/);
|
||||||
|
|
|
@ -81,6 +81,8 @@ import GroupAvatar from "../views/avatars/GroupAvatar";
|
||||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../settings/UIFeature";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -411,7 +413,7 @@ const SpaceLanding = ({ space }) => {
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
|
|
||||||
let inviteButton;
|
let inviteButton;
|
||||||
if (myMembership === "join" && space.canInvite(userId)) {
|
if (myMembership === "join" && space.canInvite(userId) && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
inviteButton = (
|
inviteButton = (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
|
|
@ -72,6 +72,8 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
export interface IDevice {
|
export interface IDevice {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
@ -393,7 +395,7 @@ const UserOptionsSection: React.FC<{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canInvite && (!member || !member.membership || member.membership === 'leave')) {
|
if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
|
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
|
||||||
const onInviteUserButton = async () => {
|
const onInviteUserButton = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -44,6 +44,8 @@ import MemberTile from "./MemberTile";
|
||||||
import BaseAvatar from '../avatars/BaseAvatar';
|
import BaseAvatar from '../avatars/BaseAvatar';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
const getSearchQueryLSKey = (roomId: string) => `mx_MemberList_searchQuarry_${roomId}`;
|
const getSearchQueryLSKey = (roomId: string) => `mx_MemberList_searchQuarry_${roomId}`;
|
||||||
|
|
||||||
|
@ -530,7 +532,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
const room = cli.getRoom(this.props.roomId);
|
const room = cli.getRoom(this.props.roomId);
|
||||||
let inviteButton;
|
let inviteButton;
|
||||||
|
|
||||||
if (room && room.getMyMembership() === 'join') {
|
if (room?.getMyMembership() === 'join' && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
let inviteButtonText = _t("Invite to this room");
|
let inviteButtonText = _t("Invite to this room");
|
||||||
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
|
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
|
||||||
if (chat && chat.roomId === this.props.roomId) {
|
if (chat && chat.roomId === this.props.roomId) {
|
||||||
|
|
|
@ -28,15 +28,17 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import MiniAvatarUploader, { AVATAR_SIZE } from "../elements/MiniAvatarUploader";
|
import MiniAvatarUploader, { AVATAR_SIZE } from "../elements/MiniAvatarUploader";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
|
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
import { showSpaceInvite } from "../../../utils/space";
|
import { showSpaceInvite } from "../../../utils/space";
|
||||||
import { privateShouldBeEncrypted } from "../../../createRoom";
|
import { privateShouldBeEncrypted } from "../../../createRoom";
|
||||||
import EventTileBubble from "../messages/EventTileBubble";
|
import EventTileBubble from "../messages/EventTileBubble";
|
||||||
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
|
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
|
function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
|
||||||
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
|
const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
|
||||||
|
@ -150,7 +152,7 @@ const NewRoomIntro = () => {
|
||||||
{ _t("Invite to just this room") }
|
{ _t("Invite to just this room") }
|
||||||
</AccessibleButton> }
|
</AccessibleButton> }
|
||||||
</div>;
|
</div>;
|
||||||
} else if (room.canInvite(cli.getUserId())) {
|
} else if (room.canInvite(cli.getUserId()) && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
buttons = <div className="mx_NewRoomIntro_buttons">
|
buttons = <div className="mx_NewRoomIntro_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_NewRoomIntro_inviteButton"
|
className="mx_NewRoomIntro_inviteButton"
|
||||||
|
|
|
@ -49,6 +49,8 @@ import { showAddExistingRooms, showCreateNewRoom, showSpaceInvite } from "../../
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import RoomAvatar from "../avatars/RoomAvatar";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onKeyDown: (ev: React.KeyboardEvent) => void;
|
onKeyDown: (ev: React.KeyboardEvent) => void;
|
||||||
|
@ -133,6 +135,9 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
|
||||||
MatrixClientPeg.get().getUserId());
|
MatrixClientPeg.get().getUserId());
|
||||||
|
|
||||||
return <IconizedContextMenuOptionList first>
|
return <IconizedContextMenuOptionList first>
|
||||||
|
{
|
||||||
|
shouldShowComponent(UIComponent.CreateRooms)
|
||||||
|
? (<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Create new room")}
|
label={_t("Create new room")}
|
||||||
iconClassName="mx_RoomList_iconPlus"
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
|
@ -159,6 +164,9 @@ const TAG_AESTHETICS: ITagAestheticsMap = {
|
||||||
tooltip={canAddRooms ? undefined
|
tooltip={canAddRooms ? undefined
|
||||||
: _t("You do not have permissions to add rooms to this space")}
|
: _t("You do not have permissions to add rooms to this space")}
|
||||||
/>
|
/>
|
||||||
|
</>)
|
||||||
|
: null
|
||||||
|
}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Explore rooms")}
|
label={_t("Explore rooms")}
|
||||||
iconClassName="mx_RoomList_iconBrowse"
|
iconClassName="mx_RoomList_iconBrowse"
|
||||||
|
|
|
@ -55,6 +55,8 @@ import { ListNotificationState } from "../../../stores/notifications/ListNotific
|
||||||
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
import IconizedContextMenu from "../context_menus/IconizedContextMenu";
|
||||||
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
|
const SHOW_N_BUTTON_HEIGHT = 28; // As defined by CSS
|
||||||
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
|
const RESIZE_HANDLE_HEIGHT = 4; // As defined by CSS
|
||||||
|
@ -675,7 +677,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let addRoomButton = null;
|
let addRoomButton = null;
|
||||||
if (!!this.props.onAddRoom) {
|
if (!!this.props.onAddRoom && shouldShowComponent(UIComponent.CreateRooms)) {
|
||||||
addRoomButton = (
|
addRoomButton = (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
@ -687,6 +689,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.props.addRoomContextMenu) {
|
} else if (this.props.addRoomContextMenu) {
|
||||||
|
// We assume that shouldShowComponent() is checked by the context menu itself.
|
||||||
addRoomButton = (
|
addRoomButton = (
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import { copyPlaintext } from "../../../utils/strings";
|
||||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { showRoomInviteDialog } from "../../../RoomInvite";
|
import { showRoomInviteDialog } from "../../../RoomInvite";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
|
||||||
|
import { UIComponent } from "../../../settings/UIFeature";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -51,7 +53,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
||||||
<h3>{ _t("Share invite link") }</h3>
|
<h3>{ _t("Share invite link") }</h3>
|
||||||
<span>{ copiedText }</span>
|
<span>{ copiedText }</span>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) ? <AccessibleButton
|
{ space.canInvite(MatrixClientPeg.get()?.getUserId()) && shouldShowComponent(UIComponent.InviteUsers)
|
||||||
|
? <AccessibleButton
|
||||||
className="mx_SpacePublicShare_inviteButton"
|
className="mx_SpacePublicShare_inviteButton"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onFinished) onFinished();
|
if (onFinished) onFinished();
|
||||||
|
@ -60,7 +63,8 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => {
|
||||||
>
|
>
|
||||||
<h3>{ _t("Invite people") }</h3>
|
<h3>{ _t("Invite people") }</h3>
|
||||||
<span>{ _t("Invite with email or username") }</span>
|
<span>{ _t("Invite with email or username") }</span>
|
||||||
</AccessibleButton> : null }
|
</AccessibleButton>
|
||||||
|
: null }
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
51
src/customisations/ComponentVisibility.ts
Normal file
51
src/customisations/ComponentVisibility.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Dev note: this customisation point is heavily inspired by UIFeature flags, though
|
||||||
|
// with an intention of being used for more complex switching on whether or not a feature
|
||||||
|
// should be shown.
|
||||||
|
|
||||||
|
// Populate this class with the details of your customisations when copying it.
|
||||||
|
|
||||||
|
import { UIComponent } from "../settings/UIFeature";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not the active MatrixClient user should be able to use
|
||||||
|
* the given UI component. If shown, the user might still not be able to use the
|
||||||
|
* component depending on their contextual permissions. For example, invite options
|
||||||
|
* might be shown to the user but they won't have permission to invite users to
|
||||||
|
* the current room: the button will appear disabled.
|
||||||
|
* @param {UIComponent} component The component to check visibility for.
|
||||||
|
* @returns {boolean} True (default) if the user is able to see the component, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
function shouldShowComponent(component: UIComponent): boolean {
|
||||||
|
return true; // default to visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// This interface summarises all available customisation points and also marks
|
||||||
|
// them all as optional. This allows customisers to only define and export the
|
||||||
|
// customisations they need while still maintaining type safety.
|
||||||
|
export interface IComponentVisibilityCustomisations {
|
||||||
|
shouldShowComponent?: typeof shouldShowComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A real customisation module will define and export one or more of the
|
||||||
|
// customisation points that make up the interface above.
|
||||||
|
export const ComponentVisibilityCustomisations: IComponentVisibilityCustomisations = {
|
||||||
|
// while we don't specify the functions here, their defaults are described
|
||||||
|
// in their pseudo-implementations above.
|
||||||
|
};
|
22
src/customisations/helpers/UIComponents.ts
Normal file
22
src/customisations/helpers/UIComponents.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
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 { UIComponent } from "../../settings/UIFeature";
|
||||||
|
import { ComponentVisibilityCustomisations } from "../ComponentVisibility";
|
||||||
|
|
||||||
|
export function shouldShowComponent(component: UIComponent): boolean {
|
||||||
|
return ComponentVisibilityCustomisations.shouldShowComponent?.(component) ?? true;
|
||||||
|
}
|
|
@ -33,3 +33,8 @@ export enum UIFeature {
|
||||||
AdvancedSettings = "UIFeature.advancedSettings",
|
AdvancedSettings = "UIFeature.advancedSettings",
|
||||||
RoomHistorySettings = "UIFeature.roomHistorySettings",
|
RoomHistorySettings = "UIFeature.roomHistorySettings",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum UIComponent {
|
||||||
|
InviteUsers = "UIComponent.sendInvites",
|
||||||
|
CreateRooms = "UIComponent.roomCreation",
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue