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 {SettingLevel} from "../../../settings/SettingLevel";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import QuestionDialog from "../dialogs/QuestionDialog"; import QuestionDialog from "../dialogs/QuestionDialog";
import {WidgetType} from "../../../widgets/WidgetType";
interface IProps extends React.ComponentProps<typeof IconizedContextMenu> { interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
app: IApp; app: IApp;
userWidget?: boolean;
showUnpin?: boolean; showUnpin?: boolean;
// override delete handler // override delete handler
onDeleteClick?(): void; 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 {room, roomId} = useContext(RoomContext);
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id); const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id);
const canModify = WidgetUtils.canUserModifyWidgets(roomId); const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId);
let unpinButton; let unpinButton;
if (showUnpin) { if (showUnpin) {
@ -80,7 +89,7 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
let deleteButton; let deleteButton;
if (onDeleteClick || canModify) { if (onDeleteClick || canModify) {
const onDeleteClick = () => { const onDeleteClickDefault = () => {
// Show delete confirmation dialog // Show delete confirmation dialog
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
title: _t("Delete Widget"), title: _t("Delete Widget"),
@ -97,21 +106,27 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
}; };
deleteButton = <IconizedContextMenuOption deleteButton = <IconizedContextMenuOption
onClick={onDeleteClick || onDeleteClick} onClick={onDeleteClick || onDeleteClickDefault}
label={_t("Remove for everyone")} label={userWidget ? _t("Remove") : _t("Remove for everyone")}
/>; />;
} }
const onRevokeClick = () => { const isLocalWidget = WidgetType.JITSI.matches(app.type);
console.info("Revoking permission for widget to load: " + app.eventId); let revokeButton;
const current = SettingsStore.getValue("allowedWidgets", roomId); if (!userWidget && !isLocalWidget) {
current[app.eventId] = false; const onRevokeClick = () => {
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => { console.info("Revoking permission for widget to load: " + app.eventId);
console.error(err); const current = SettingsStore.getValue("allowedWidgets", roomId);
// We don't really need to do anything about this - the user will just hit the button again. current[app.eventId] = false;
}); SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).catch(err => {
onFinished(); 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}> return <IconizedContextMenu {...props} chevronFace={ChevronFace.None} onFinished={onFinished}>
<IconizedContextMenuOptionList> <IconizedContextMenuOptionList>
@ -119,10 +134,10 @@ const RoomWidgetContextMenu: React.FC<IProps> = ({ onFinished, app, onDeleteClic
{ snapshotButton } { snapshotButton }
{ editButton } { editButton }
{ deleteButton } { deleteButton }
<IconizedContextMenuOption onClick={onRevokeClick} label={_t("Remove for me")} /> { revokeButton }
</IconizedContextMenuOptionList> </IconizedContextMenuOptionList>
</IconizedContextMenu>; </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 {StopGapWidget} from "../../../stores/widgets/StopGapWidget";
import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions"; import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions";
import {MatrixCapabilities} from "matrix-widget-api"; 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 { export default class AppTile extends React.Component {
constructor(props) { constructor(props) {
@ -62,6 +62,9 @@ export default class AppTile extends React.Component {
if (this._usingLocalWidget()) return true; if (this._usingLocalWidget()) return true;
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId); const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", props.room.roomId);
if (currentlyAllowedWidgets[props.app.eventId] === undefined) {
return props.userId === props.creatorUserId;
}
return !!currentlyAllowedWidgets[props.app.eventId]; return !!currentlyAllowedWidgets[props.app.eventId];
}; };
@ -78,7 +81,7 @@ export default class AppTile extends React.Component {
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey), loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
// Assume that widget has permission to load if we are the user who // 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 // 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, error: null,
widgetPageTitle: newProps.widgetPageTitle, widgetPageTitle: newProps.widgetPageTitle,
menuDisplayed: false, menuDisplayed: false,
@ -86,8 +89,7 @@ export default class AppTile extends React.Component {
} }
onAllowedWidgetsChange = () => { onAllowedWidgetsChange = () => {
const hasPermissionToLoad = const hasPermissionToLoad = this.hasPermissionToLoad(this.props);
this.props.userId === this.prop.creatorUserId || this.hasPermissionToLoad(this.props);
if (this.state.hasPermissionToLoad && !hasPermissionToLoad) { if (this.state.hasPermissionToLoad && !hasPermissionToLoad) {
// Force the widget to be non-persistent (able to be deleted/forgotten) // 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} app={this.props.app}
onFinished={this._closeContextMenu} onFinished={this._closeContextMenu}
showUnpin={!this.props.userWidget} 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 TextWithTooltip from "../elements/TextWithTooltip";
import BaseAvatar from "../avatars/BaseAvatar"; import BaseAvatar from "../avatars/BaseAvatar";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; 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 { E2EStatus } from "../../../utils/ShieldUtils";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import {UIFeature} from "../../../settings/UIFeature"; import {UIFeature} from "../../../settings/UIFeature";
import {ContextMenuButton} from "../../../accessibility/context_menu/ContextMenuButton"; import {ContextMenuButton} from "../../../accessibility/context_menu/ContextMenuButton";
import {ChevronFace, useContextMenu} from "../../structures/ContextMenu"; import {ChevronFace, useContextMenu} from "../../structures/ContextMenu";
import RoomWidgetContextMenu from "../context_menus/RoomWidgetContextMenu"; import WidgetContextMenu from "../context_menus/WidgetContextMenu";
interface IProps { interface IProps {
room: Room; room: Room;
@ -129,7 +129,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app }) => {
let contextMenu; let contextMenu;
if (menuDisplayed) { if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect(); const rect = handle.current.getBoundingClientRect();
contextMenu = <RoomWidgetContextMenu contextMenu = <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
right={window.innerWidth - rect.right} right={window.innerWidth - rect.right}
bottom={window.innerHeight - rect.top} 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}> return <div className="mx_RoomSummaryCard_widgetRow" ref={handle}>
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_app" 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, mx_RoomSummaryCard_app_pinned: isPinned,
})} })}
onClick={togglePin} onClick={togglePin}
title={isPinned ? _t("Unpin") : _t("Pin")} title={pinTitle}
disabled={cannotPin}
/> />
<ContextMenuButton <ContextMenuButton

View file

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

View file

@ -1270,8 +1270,9 @@
"Yours, or the other users session": "Yours, or the other users session", "Yours, or the other users session": "Yours, or the other users session",
"Members": "Members", "Members": "Members",
"Room Info": "Room Info", "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": "Unpin",
"Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel",
"Options": "Options", "Options": "Options",
"Widgets": "Widgets", "Widgets": "Widgets",
"Edit widgets, bridges & bots": "Edit widgets, bridges & bots", "Edit widgets, bridges & bots": "Edit widgets, bridges & bots",
@ -1898,17 +1899,17 @@
"Source URL": "Source URL", "Source URL": "Source URL",
"Collapse Reply Thread": "Collapse Reply Thread", "Collapse Reply Thread": "Collapse Reply Thread",
"Report Content": "Report Content", "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", "Take a picture": "Take a picture",
"Delete Widget": "Delete Widget", "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?", "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", "Delete widget": "Delete widget",
"Remove for everyone": "Remove for everyone", "Remove for everyone": "Remove for everyone",
"Remove for me": "Remove for me", "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", "This room is public": "This room is public",
"Away": "Away", "Away": "Away",
"User Status": "User Status", "User Status": "User Status",

View file

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