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

Hide widget menu button if it there are no options available #11257

Merged
merged 2 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 73 additions & 19 deletions src/components/views/context_menus/WidgetContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ limitations under the License.
*/

import React, { ComponentProps, useContext } from "react";
import { IWidget, MatrixCapabilities } from "matrix-widget-api";
import { ClientWidgetApi, IWidget, MatrixCapabilities } from "matrix-widget-api";
import { logger } from "matrix-js-sdk/src/logger";
import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle";
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";

import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
import { ChevronFace } from "../../structures/ContextMenu";
Expand Down Expand Up @@ -48,6 +49,69 @@ interface IProps extends Omit<ComponentProps<typeof IconizedContextMenu>, "child
onEditClick?(): void;
}

const showStreamAudioStreamButton = (app: IWidget): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some doc comments where relevant here?
It sometimes has logic that is not straightforward to follow

return !!getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type);
};

const showEditButton = (app: IWidget, canModify: boolean): boolean => {
return canModify && WidgetUtils.isManagedByManager(app);
};

const showRevokeButton = (
cli: MatrixClient,
roomId: string | undefined,
app: IWidget,
userWidget: boolean | undefined,
): boolean => {
const isAllowedWidget =
(isAppWidget(app) &&
app.eventId !== undefined &&
(SettingsStore.getValue("allowedWidgets", roomId)[app.eventId] ?? false)) ||
app.creatorUserId === cli?.getUserId();

const isLocalWidget = WidgetType.JITSI.matches(app.type);
return !userWidget && !isLocalWidget && isAllowedWidget;
};

const showDeleteButton = (canModify: boolean, onDeleteClick: undefined | (() => void)): boolean => {
return !!onDeleteClick || canModify;
};

const showSnapshotButton = (widgetMessaging: ClientWidgetApi | undefined): boolean => {
return (
SettingsStore.getValue<boolean>("enableWidgetScreenshots") &&
!!widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)
);
};

const showMoveButtons = (app: IWidget, room: Room | undefined, showUnpin: boolean | undefined): [boolean, boolean] => {
if (!showUnpin) return [false, false];

const pinnedWidgets = room ? WidgetLayoutStore.instance.getContainerWidgets(room, Container.Top) : [];
const widgetIndex = pinnedWidgets.findIndex((widget) => widget.id === app.id);
return [widgetIndex > 0, widgetIndex < pinnedWidgets.length - 1];
};

export const showContextMenu = (
cli: MatrixClient,
room: Room | undefined,
app: IWidget,
userWidget: boolean,
showUnpin: boolean,
onDeleteClick: (() => void) | undefined,
): boolean => {
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, room?.roomId);
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(app));
return (
showStreamAudioStreamButton(app) ||
showEditButton(app, canModify) ||
showRevokeButton(cli, room?.roomId, app, userWidget) ||
showDeleteButton(canModify, onDeleteClick) ||
showSnapshotButton(widgetMessaging) ||
showMoveButtons(app, room, showUnpin).some(Boolean)
);
};

export const WidgetContextMenu: React.FC<IProps> = ({
onFinished,
app,
Expand All @@ -64,7 +128,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, roomId);

let streamAudioStreamButton: JSX.Element | undefined;
if (roomId && getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type)) {
if (roomId && showStreamAudioStreamButton(app)) {
const onStreamAudioClick = async (): Promise<void> => {
try {
await startJitsiAudioLivestream(cli, widgetMessaging!, roomId);
Expand All @@ -84,11 +148,8 @@ export const WidgetContextMenu: React.FC<IProps> = ({
);
}

const pinnedWidgets = room ? WidgetLayoutStore.instance.getContainerWidgets(room, Container.Top) : [];
const widgetIndex = pinnedWidgets.findIndex((widget) => widget.id === app.id);

let editButton: JSX.Element | undefined;
if (canModify && WidgetUtils.isManagedByManager(app)) {
if (showEditButton(app, canModify)) {
const _onEditClick = (): void => {
if (onEditClick) {
onEditClick();
Expand All @@ -102,8 +163,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
}

let snapshotButton: JSX.Element | undefined;
const screenshotsEnabled = SettingsStore.getValue("enableWidgetScreenshots");
if (screenshotsEnabled && widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)) {
if (showSnapshotButton(widgetMessaging)) {
const onSnapshotClick = (): void => {
widgetMessaging
?.takeScreenshot()
Expand All @@ -123,7 +183,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
}

let deleteButton: JSX.Element | undefined;
if (onDeleteClick || canModify) {
if (showDeleteButton(canModify, onDeleteClick)) {
const _onDeleteClick = (): void => {
if (onDeleteClick) {
onDeleteClick();
Expand Down Expand Up @@ -154,15 +214,8 @@ export const WidgetContextMenu: React.FC<IProps> = ({
);
}

const isAllowedWidget =
(isAppWidget(app) &&
app.eventId !== undefined &&
(SettingsStore.getValue("allowedWidgets", roomId)[app.eventId] ?? false)) ||
app.creatorUserId === cli.getUserId();

const isLocalWidget = WidgetType.JITSI.matches(app.type);
let revokeButton: JSX.Element | undefined;
if (!userWidget && !isLocalWidget && isAllowedWidget) {
if (showRevokeButton(cli, roomId, app, userWidget)) {
const opts: ApprovalOpts = { approved: undefined };
ModuleRunner.instance.invoke(WidgetLifecycle.PreLoadRequest, opts, new ElementWidget(app));

Expand All @@ -185,8 +238,9 @@ export const WidgetContextMenu: React.FC<IProps> = ({
}
}

const [showMoveLeftButton, showMoveRightButton] = showMoveButtons(app, room, showUnpin);
let moveLeftButton: JSX.Element | undefined;
if (showUnpin && widgetIndex > 0) {
if (showMoveLeftButton) {
const onClick = (): void => {
if (!room) throw new Error("room must be defined");
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1);
Expand All @@ -197,7 +251,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
}

let moveRightButton: JSX.Element | undefined;
if (showUnpin && widgetIndex < pinnedWidgets.length - 1) {
if (showMoveRightButton) {
const onClick = (): void => {
if (!room) throw new Error("room must be defined");
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, 1);
Expand Down
31 changes: 21 additions & 10 deletions src/components/views/elements/AppTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu";
import PersistedElement, { getPersistKey } from "./PersistedElement";
import { WidgetType } from "../../../widgets/WidgetType";
import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget";
import { WidgetContextMenu } from "../context_menus/WidgetContextMenu";
import { showContextMenu, WidgetContextMenu } from "../context_menus/WidgetContextMenu";
import WidgetAvatar from "../avatars/WidgetAvatar";
import LegacyCallHandler from "../../../LegacyCallHandler";
import { IApp, isAppWidget } from "../../../stores/WidgetStore";
Expand Down Expand Up @@ -107,6 +107,7 @@ interface IState {
error: Error | null;
menuDisplayed: boolean;
requiresClient: boolean;
hasContextMenuOptions: boolean;
}

export default class AppTile extends React.Component<IProps, IState> {
Expand Down Expand Up @@ -249,6 +250,14 @@ export default class AppTile extends React.Component<IProps, IState> {
error: null,
menuDisplayed: false,
requiresClient: this.determineInitialRequiresClientState(),
hasContextMenuOptions: showContextMenu(
this.context,
this.props.room,
newProps.app,
newProps.userWidget,
!newProps.userWidget,
newProps.onDeleteClick,
),
};
}

Expand Down Expand Up @@ -770,15 +779,17 @@ export default class AppTile extends React.Component<IProps, IState> {
<PopoutIcon className="mx_Icon mx_Icon_12 mx_Icon--stroke" />
</AccessibleButton>
)}
<ContextMenuButton
className="mx_AppTileMenuBar_widgets_button"
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
inputRef={this.contextMenuButton}
onClick={this.onContextMenuClick}
>
<MenuIcon className="mx_Icon mx_Icon_12" />
</ContextMenuButton>
{this.state.hasContextMenuOptions && (
<ContextMenuButton
className="mx_AppTileMenuBar_widgets_button"
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
inputRef={this.contextMenuButton}
onClick={this.onContextMenuClick}
>
<MenuIcon className="mx_Icon mx_Icon_12" />
</ContextMenuButton>
)}
</span>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,19 +139,6 @@ exports[`AppTile for a pinned widget should render 1`] = `
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
</span>
</div>
<div
Expand Down Expand Up @@ -214,19 +201,6 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
</span>
</div>
<div
Expand Down Expand Up @@ -367,19 +341,6 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
class="mx_Icon mx_Icon_12"
/>
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_AppTileMenuBar_widgets_button"
role="button"
tabindex="0"
title="Options"
>
<div
class="mx_Icon mx_Icon_12"
/>
</div>
</span>
</div>
<div
Expand Down