mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-15 20:54:59 +08:00
Improve the context menu
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
b2dc5542b3
commit
37558f1f0d
@ -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;
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user