Improve the context menu

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-10-12 09:51:49 +01:00
parent b2dc5542b3
commit 37558f1f0d
6 changed files with 63 additions and 34 deletions

View file

@ -29,19 +29,28 @@ import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
import Modal from "../../../Modal";
import QuestionDialog from "../dialogs/QuestionDialog";
import {WidgetType} from "../../../widgets/WidgetType";
interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
app: IApp;
userWidget?: boolean;
showUnpin?: boolean;
// override delete handler
onDeleteClick?(): void;
}
const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClick, showUnpin, ...props}) => {
const WidgetContextMenu: React.FC<IProps> = ({
onFinished,
app,
userWidget,
onDeleteClick,
showUnpin,
...props
}) => {
const {room, roomId} = useContext(RoomContext);
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id);
const canModify = WidgetUtils.canUserModifyWidgets(roomId);
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId);
let unpinButton;
if (showUnpin) {
@ -80,7 +89,7 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
let deleteButton;
if (onDeleteClick || canModify) {
const onDeleteClick = () => {
const onDeleteClickDefault = () => {
// Show delete confirmation dialog
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
title: _t("Delete Widget"),
@ -97,21 +106,27 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
};
deleteButton = <IconizedContextMenuOption
onClick={onDeleteClick || onDeleteClick}
label={_t("Remove for everyone")}
onClick={onDeleteClick || onDeleteClickDefault}
label={userWidget ? _t("Remove") : _t("Remove for everyone")}
/>;
}
const onRevokeClick = () => {
console.info("Revoking permission for widget to load: " + app.eventId);
const current = SettingsStore.getValue("allowedWidgets", roomId);
current[app.eventId] = false;
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => {
console.error(err);
// We don't really need to do anything about this - the user will just hit the button again.
});
onFinished();
};
const isLocalWidget = WidgetType.JITSI.matches(app.type);
let revokeButton;
if (!userWidget && !isLocalWidget) {
const onRevokeClick = () => {
console.info("Revoking permission for widget to load: " + app.eventId);
const current = SettingsStore.getValue("allowedWidgets", roomId);
current[app.eventId] = false;
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => {
console.error(err);
// We don't really need to do anything about this - the user will just hit the button again.
});
onFinished();
};
revokeButton = <IconizedContextMenuOption onClick={onRevokeClick} label={_t("Remove for me")} />;
}
return <IconizedContextMenu {...props} chevronFace={ChevronFace.None} onFinished={onFinished}>
<IconizedContextMenuOptionList>
@ -119,10 +134,10 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
{ snapshotButton }
{ editButton }
{ deleteButton }
<IconizedContextMenuOption onClick={onRevokeClick} label={_t("Remove for me")} />
{ revokeButton }
</IconizedContextMenuOptionList>
</IconizedContextMenu>;
};
export default RoomWidgetContextMenu;
export default WidgetContextMenu;

View file

@ -38,7 +38,7 @@ import {SettingLevel} from "../../../settings/SettingLevel";
import {StopGapWidget} from "../../../stores/widgets/StopGapWidget";
import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions";
import {MatrixCapabilities} from "matrix-widget-api";
import RoomWidgetContextMenu from "../context_menus/RoomWidgetContextMenu";
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu";
export default class AppTile extends React.Component {
constructor(props) {
@ -62,6 +62,9 @@ export default class AppTile extends React.Component {
if (this._usingLocalWidget()) return true;
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId);
if (currentlyAllowedWidgets[props.app.eventId] === undefined) {
return props.userId === props.creatorUserId;
}
return !!currentlyAllowedWidgets[props.app.eventId];
};
@ -78,7 +81,7 @@ export default class AppTile extends React.Component {
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
// Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user
hasPermissionToLoad: newProps.userId === newProps.creatorUserId || this.hasPermissionToLoad(newProps),
hasPermissionToLoad: this.hasPermissionToLoad(newProps),
error: null,
widgetPageTitle: newProps.widgetPageTitle,
menuDisplayed: false,
@ -86,8 +89,7 @@ export default class AppTile extends React.Component {
}
onAllowedWidgetsChange = () => {
const hasPermissionToLoad =
this.props.userId === this.prop.creatorUserId || this.hasPermissionToLoad(this.props);
const hasPermissionToLoad = this.hasPermissionToLoad(this.props);
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
// Force the widget to be non-persistent (able to be deleted/forgotten)
@ -399,6 +401,7 @@ export default class AppTile extends React.Component {
app={this.props.app}
onFinished={this._closeContextMenu}
showUnpin={!this.props.userWidget}
userWidget={this.props.userWidget}
/>
);
}

View file

@ -39,13 +39,13 @@ import SettingsStore from "../../../settings/SettingsStore";
import TextWithTooltip from "../elements/TextWithTooltip";
import BaseAvatar from "../avatars/BaseAvatar";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import WidgetStore, {IApp} from "../../../stores/WidgetStore";
import WidgetStore, {IApp, MAX_PINNED} from "../../../stores/WidgetStore";
import { E2EStatus } from "../../../utils/ShieldUtils";
import RoomContext from "../../../contexts/RoomContext";
import {UIFeature} from "../../../settings/UIFeature";
import {ContextMenuButton} from "../../../accessibility/context_menu/ContextMenuButton";
import {ChevronFace, useContextMenu} from "../../structures/ContextMenu";
import RoomWidgetContextMenu from "../context_menus/RoomWidgetContextMenu";
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
interface IProps {
room: Room;
@ -129,7 +129,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app }) => {
let contextMenu;
if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect();
contextMenu = <RoomWidgetContextMenu
contextMenu = <WidgetContextMenu
chevronFace={ChevronFace.None}
right={window.innerWidth - rect.right}
bottom={window.innerHeight - rect.top}
@ -138,6 +138,15 @@ const AppRow: React.FC<IAppRowProps> = ({ app }) => {
/>;
}
const cannotPin = !isPinned && !WidgetStore.instance.canPin(app.id);
let pinTitle: string;
if (cannotPin) {
pinTitle = _t("You can only pin up to %(count)s widgets", { count: MAX_PINNED });
} else {
pinTitle = isPinned ? _t("Unpin") : _t("Pin");
}
return <div className="mx_RoomSummaryCard_widgetRow" ref={handle}>
<AccessibleTooltipButton
className="mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_app"
@ -157,7 +166,8 @@ const AppRow: React.FC<IAppRowProps> = ({ app }) => {
mx_RoomSummaryCard_app_pinned: isPinned,
})}
onClick={togglePin}
title={isPinned ? _t("Unpin") : _t("Pin")}
title={pinTitle}
disabled={cannotPin}
/>
<ContextMenuButton

View file

@ -29,7 +29,7 @@ import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPa
import {Action} from "../../../dispatcher/actions";
import WidgetStore from "../../../stores/WidgetStore";
import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu";
import RoomWidgetContextMenu from "../context_menus/RoomWidgetContextMenu";
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
interface IProps {
room: Room;
@ -63,7 +63,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect();
contextMenu = (
<RoomWidgetContextMenu
<WidgetContextMenu
chevronFace={ChevronFace.None}
right={window.innerWidth - rect.right - 12}
top={rect.bottom + 12}

View file

@ -1270,8 +1270,9 @@
"Yours, or the other users session": "Yours, or the other users session",
"Members": "Members",
"Room Info": "Room Info",
"Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel",
"You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets",
"Unpin": "Unpin",
"Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel",
"Options": "Options",
"Widgets": "Widgets",
"Edit widgets, bridges & bots": "Edit widgets, bridges & bots",
@ -1898,17 +1899,17 @@
"Source URL": "Source URL",
"Collapse Reply Thread": "Collapse Reply Thread",
"Report Content": "Report Content",
"Clear status": "Clear status",
"Update status": "Update status",
"Set status": "Set status",
"Set a new status...": "Set a new status...",
"View Community": "View Community",
"Take a picture": "Take a picture",
"Delete Widget": "Delete Widget",
"Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?",
"Delete widget": "Delete widget",
"Remove for everyone": "Remove for everyone",
"Remove for me": "Remove for me",
"Clear status": "Clear status",
"Update status": "Update status",
"Set status": "Set status",
"Set a new status...": "Set a new status...",
"View Community": "View Community",
"This room is public": "This room is public",
"Away": "Away",
"User Status": "User Status",

View file

@ -46,7 +46,7 @@ interface IRoomWidgets {
pinned: Record<string, boolean>;
}
const MAX_PINNED = 3;
export const MAX_PINNED = 3;
// TODO consolidate WidgetEchoStore into this
// TODO consolidate ActiveWidgetStore into this