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

* Hide widget menu button if it there are no options available

* Update snapshots
pull/28217/head
Michael Telatynski 2023-07-17 11:37:07 +01:00 committed by GitHub
parent 5d4153fa64
commit 8b8ca425d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 68 deletions

View File

@ -15,9 +15,10 @@ limitations under the License.
*/ */
import React, { ComponentProps, useContext } from "react"; 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 { logger } from "matrix-js-sdk/src/logger";
import { ApprovalOpts, WidgetLifecycle } from "@matrix-org/react-sdk-module-api/lib/lifecycles/WidgetLifecycle"; 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 IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
import { ChevronFace } from "../../structures/ContextMenu"; import { ChevronFace } from "../../structures/ContextMenu";
@ -48,6 +49,69 @@ interface IProps extends Omit<ComponentProps<typeof IconizedContextMenu>, "child
onEditClick?(): void; onEditClick?(): void;
} }
const showStreamAudioStreamButton = (app: IWidget): boolean => {
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> = ({ export const WidgetContextMenu: React.FC<IProps> = ({
onFinished, onFinished,
app, app,
@ -64,7 +128,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, roomId); const canModify = userWidget || WidgetUtils.canUserModifyWidgets(cli, roomId);
let streamAudioStreamButton: JSX.Element | undefined; let streamAudioStreamButton: JSX.Element | undefined;
if (roomId && getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type)) { if (roomId && showStreamAudioStreamButton(app)) {
const onStreamAudioClick = async (): Promise<void> => { const onStreamAudioClick = async (): Promise<void> => {
try { try {
await startJitsiAudioLivestream(cli, widgetMessaging!, roomId); await startJitsiAudioLivestream(cli, widgetMessaging!, roomId);
@ -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; let editButton: JSX.Element | undefined;
if (canModify && WidgetUtils.isManagedByManager(app)) { if (showEditButton(app, canModify)) {
const _onEditClick = (): void => { const _onEditClick = (): void => {
if (onEditClick) { if (onEditClick) {
onEditClick(); onEditClick();
@ -102,8 +163,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
} }
let snapshotButton: JSX.Element | undefined; let snapshotButton: JSX.Element | undefined;
const screenshotsEnabled = SettingsStore.getValue("enableWidgetScreenshots"); if (showSnapshotButton(widgetMessaging)) {
if (screenshotsEnabled && widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)) {
const onSnapshotClick = (): void => { const onSnapshotClick = (): void => {
widgetMessaging widgetMessaging
?.takeScreenshot() ?.takeScreenshot()
@ -123,7 +183,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
} }
let deleteButton: JSX.Element | undefined; let deleteButton: JSX.Element | undefined;
if (onDeleteClick || canModify) { if (showDeleteButton(canModify, onDeleteClick)) {
const _onDeleteClick = (): void => { const _onDeleteClick = (): void => {
if (onDeleteClick) { if (onDeleteClick) {
onDeleteClick(); onDeleteClick();
@ -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; let revokeButton: JSX.Element | undefined;
if (!userWidget && !isLocalWidget && isAllowedWidget) { if (showRevokeButton(cli, roomId, app, userWidget)) {
const opts: ApprovalOpts = { approved: undefined }; const opts: ApprovalOpts = { approved: undefined };
ModuleRunner.instance.invoke(WidgetLifecycle.PreLoadRequest, opts, new ElementWidget(app)); ModuleRunner.instance.invoke(WidgetLifecycle.PreLoadRequest, opts, new ElementWidget(app));
@ -185,8 +238,9 @@ export const WidgetContextMenu: React.FC<IProps> = ({
} }
} }
const [showMoveLeftButton, showMoveRightButton] = showMoveButtons(app, room, showUnpin);
let moveLeftButton: JSX.Element | undefined; let moveLeftButton: JSX.Element | undefined;
if (showUnpin && widgetIndex > 0) { if (showMoveLeftButton) {
const onClick = (): void => { const onClick = (): void => {
if (!room) throw new Error("room must be defined"); if (!room) throw new Error("room must be defined");
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1); WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1);
@ -197,7 +251,7 @@ export const WidgetContextMenu: React.FC<IProps> = ({
} }
let moveRightButton: JSX.Element | undefined; let moveRightButton: JSX.Element | undefined;
if (showUnpin && widgetIndex < pinnedWidgets.length - 1) { if (showMoveRightButton) {
const onClick = (): void => { const onClick = (): void => {
if (!room) throw new Error("room must be defined"); if (!room) throw new Error("room must be defined");
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, 1); WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, 1);

View File

@ -36,7 +36,7 @@ import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu";
import PersistedElement, { getPersistKey } from "./PersistedElement"; import PersistedElement, { getPersistKey } from "./PersistedElement";
import { WidgetType } from "../../../widgets/WidgetType"; import { WidgetType } from "../../../widgets/WidgetType";
import { ElementWidget, StopGapWidget } from "../../../stores/widgets/StopGapWidget"; 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 WidgetAvatar from "../avatars/WidgetAvatar";
import LegacyCallHandler from "../../../LegacyCallHandler"; import LegacyCallHandler from "../../../LegacyCallHandler";
import { IApp, isAppWidget } from "../../../stores/WidgetStore"; import { IApp, isAppWidget } from "../../../stores/WidgetStore";
@ -107,6 +107,7 @@ interface IState {
error: Error | null; error: Error | null;
menuDisplayed: boolean; menuDisplayed: boolean;
requiresClient: boolean; requiresClient: boolean;
hasContextMenuOptions: boolean;
} }
export default class AppTile extends React.Component<IProps, IState> { export default class AppTile extends React.Component<IProps, IState> {
@ -249,6 +250,14 @@ export default class AppTile extends React.Component<IProps, IState> {
error: null, error: null,
menuDisplayed: false, menuDisplayed: false,
requiresClient: this.determineInitialRequiresClientState(), requiresClient: this.determineInitialRequiresClientState(),
hasContextMenuOptions: showContextMenu(
this.context,
this.props.room,
newProps.app,
newProps.userWidget,
!newProps.userWidget,
newProps.onDeleteClick,
),
}; };
} }
@ -770,15 +779,17 @@ export default class AppTile extends React.Component<IProps, IState> {
<PopoutIcon className="mx_Icon mx_Icon_12 mx_Icon--stroke" /> <PopoutIcon className="mx_Icon mx_Icon_12 mx_Icon--stroke" />
</AccessibleButton> </AccessibleButton>
)} )}
<ContextMenuButton {this.state.hasContextMenuOptions && (
className="mx_AppTileMenuBar_widgets_button" <ContextMenuButton
label={_t("Options")} className="mx_AppTileMenuBar_widgets_button"
isExpanded={this.state.menuDisplayed} label={_t("Options")}
inputRef={this.contextMenuButton} isExpanded={this.state.menuDisplayed}
onClick={this.onContextMenuClick} inputRef={this.contextMenuButton}
> onClick={this.onContextMenuClick}
<MenuIcon className="mx_Icon mx_Icon_12" /> >
</ContextMenuButton> <MenuIcon className="mx_Icon mx_Icon_12" />
</ContextMenuButton>
)}
</span> </span>
</div> </div>
)} )}

View File

@ -139,19 +139,6 @@ exports[`AppTile for a pinned widget should render 1`] = `
class="mx_Icon mx_Icon_12" class="mx_Icon mx_Icon_12"
/> />
</div> </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> </span>
</div> </div>
<div <div
@ -214,19 +201,6 @@ exports[`AppTile for a pinned widget should render permission request 1`] = `
class="mx_Icon mx_Icon_12" class="mx_Icon mx_Icon_12"
/> />
</div> </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> </span>
</div> </div>
<div <div
@ -367,19 +341,6 @@ exports[`AppTile preserves non-persisted widget on container move 1`] = `
class="mx_Icon mx_Icon_12" class="mx_Icon mx_Icon_12"
/> />
</div> </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> </span>
</div> </div>
<div <div