2020-09-30 17:38:35 +03:00
|
|
|
/*
|
|
|
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import React, {useContext} from "react";
|
2020-10-07 12:36:45 +03:00
|
|
|
import {MatrixCapabilities} from "matrix-widget-api";
|
2020-09-30 17:38:35 +03:00
|
|
|
|
|
|
|
import IconizedContextMenu, {IconizedContextMenuOption, IconizedContextMenuOptionList} from "./IconizedContextMenu";
|
|
|
|
import {ChevronFace} from "../../structures/ContextMenu";
|
|
|
|
import {_t} from "../../../languageHandler";
|
2021-01-19 06:26:47 +03:00
|
|
|
import {IApp} from "../../../stores/WidgetStore";
|
2020-09-30 17:38:35 +03:00
|
|
|
import WidgetUtils from "../../../utils/WidgetUtils";
|
2020-10-07 12:36:45 +03:00
|
|
|
import {WidgetMessagingStore} from "../../../stores/widgets/WidgetMessagingStore";
|
2020-09-30 17:38:35 +03:00
|
|
|
import RoomContext from "../../../contexts/RoomContext";
|
2020-10-09 10:42:21 +03:00
|
|
|
import dis from "../../../dispatcher/dispatcher";
|
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
|
|
|
import Modal from "../../../Modal";
|
|
|
|
import QuestionDialog from "../dialogs/QuestionDialog";
|
2021-03-04 20:52:49 +03:00
|
|
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
2020-10-12 11:51:49 +03:00
|
|
|
import {WidgetType} from "../../../widgets/WidgetType";
|
2020-10-16 13:51:27 +03:00
|
|
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
2021-01-19 06:26:47 +03:00
|
|
|
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
2021-03-02 18:20:54 +03:00
|
|
|
import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream";
|
2020-09-30 17:38:35 +03:00
|
|
|
|
|
|
|
interface IProps extends React.ComponentProps<typeof IconizedContextMenu> {
|
|
|
|
app: IApp;
|
2020-10-12 11:51:49 +03:00
|
|
|
userWidget?: boolean;
|
2020-10-09 10:42:21 +03:00
|
|
|
showUnpin?: boolean;
|
|
|
|
// override delete handler
|
|
|
|
onDeleteClick?(): void;
|
2020-09-30 17:38:35 +03:00
|
|
|
}
|
|
|
|
|
2020-10-12 11:51:49 +03:00
|
|
|
const WidgetContextMenu: React.FC<IProps> = ({
|
|
|
|
onFinished,
|
|
|
|
app,
|
|
|
|
userWidget,
|
|
|
|
onDeleteClick,
|
|
|
|
showUnpin,
|
|
|
|
...props
|
|
|
|
}) => {
|
2020-10-16 13:51:27 +03:00
|
|
|
const cli = useContext(MatrixClientContext);
|
2020-10-09 10:42:21 +03:00
|
|
|
const {room, roomId} = useContext(RoomContext);
|
2020-09-30 17:38:35 +03:00
|
|
|
|
2020-10-07 12:36:45 +03:00
|
|
|
const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id);
|
2020-10-12 11:51:49 +03:00
|
|
|
const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId);
|
2020-10-09 10:42:21 +03:00
|
|
|
|
2021-03-02 18:20:54 +03:00
|
|
|
let streamAudioStreamButton;
|
2021-03-05 13:32:54 +03:00
|
|
|
if (getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type)) {
|
2021-03-04 20:52:49 +03:00
|
|
|
const onStreamAudioClick = async () => {
|
|
|
|
try {
|
|
|
|
await startJitsiAudioLivestream(widgetMessaging, roomId);
|
|
|
|
} catch (err) {
|
2021-03-05 13:34:03 +03:00
|
|
|
console.error("Failed to start livestream", err);
|
2021-03-04 20:52:49 +03:00
|
|
|
// XXX: won't i18n well, but looks like widget api only support 'message'?
|
|
|
|
const message = err.message || _t("Unable to start audio streaming.");
|
|
|
|
Modal.createTrackedDialog('WidgetContext Menu', 'Livestream failed', ErrorDialog, {
|
|
|
|
title: _t('Failed to start livestream'),
|
|
|
|
description: message,
|
|
|
|
});
|
|
|
|
}
|
2021-03-02 18:20:54 +03:00
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
streamAudioStreamButton = <IconizedContextMenuOption
|
|
|
|
onClick={onStreamAudioClick} label={_t("Start audio stream")}
|
|
|
|
/>;
|
|
|
|
}
|
|
|
|
|
2020-10-09 10:42:21 +03:00
|
|
|
let unpinButton;
|
|
|
|
if (showUnpin) {
|
|
|
|
const onUnpinClick = () => {
|
2021-01-19 06:26:47 +03:00
|
|
|
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Right);
|
2020-10-15 17:24:42 +03:00
|
|
|
onFinished();
|
2020-10-09 10:42:21 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
unpinButton = <IconizedContextMenuOption onClick={onUnpinClick} label={_t("Unpin")} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
let editButton;
|
|
|
|
if (canModify && WidgetUtils.isManagedByManager(app)) {
|
|
|
|
const onEditClick = () => {
|
|
|
|
WidgetUtils.editWidget(room, app);
|
2020-10-15 17:24:42 +03:00
|
|
|
onFinished();
|
2020-10-09 10:42:21 +03:00
|
|
|
};
|
|
|
|
|
2020-10-15 17:24:42 +03:00
|
|
|
editButton = <IconizedContextMenuOption onClick={onEditClick} label={_t("Edit")} />;
|
2020-10-09 10:42:21 +03:00
|
|
|
}
|
2020-10-07 12:36:45 +03:00
|
|
|
|
2020-09-30 17:38:35 +03:00
|
|
|
let snapshotButton;
|
2020-10-07 12:36:45 +03:00
|
|
|
if (widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)) {
|
2020-09-30 17:38:35 +03:00
|
|
|
const onSnapshotClick = () => {
|
2020-10-09 10:42:21 +03:00
|
|
|
widgetMessaging?.takeScreenshot().then(data => {
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'picture_snapshot',
|
|
|
|
file: data.screenshot,
|
|
|
|
});
|
|
|
|
}).catch(err => {
|
|
|
|
console.error("Failed to take screenshot: ", err);
|
|
|
|
});
|
2020-09-30 17:38:35 +03:00
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
|
|
|
|
snapshotButton = <IconizedContextMenuOption onClick={onSnapshotClick} label={_t("Take a picture")} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
let deleteButton;
|
2020-10-09 10:42:21 +03:00
|
|
|
if (onDeleteClick || canModify) {
|
2020-10-12 11:51:49 +03:00
|
|
|
const onDeleteClickDefault = () => {
|
2020-10-09 10:42:21 +03:00
|
|
|
// Show delete confirmation dialog
|
|
|
|
Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, {
|
|
|
|
title: _t("Delete Widget"),
|
|
|
|
description: _t(
|
|
|
|
"Deleting a widget removes it for all users in this room." +
|
|
|
|
" Are you sure you want to delete this widget?"),
|
|
|
|
button: _t("Delete widget"),
|
|
|
|
onFinished: (confirmed) => {
|
|
|
|
if (!confirmed) return;
|
|
|
|
WidgetUtils.setRoomWidget(roomId, app.id);
|
|
|
|
},
|
2020-09-30 17:38:35 +03:00
|
|
|
});
|
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
|
2020-10-09 10:42:21 +03:00
|
|
|
deleteButton = <IconizedContextMenuOption
|
2020-10-12 11:51:49 +03:00
|
|
|
onClick={onDeleteClick || onDeleteClickDefault}
|
|
|
|
label={userWidget ? _t("Remove") : _t("Remove for everyone")}
|
2020-10-09 10:42:21 +03:00
|
|
|
/>;
|
2020-09-30 17:38:35 +03:00
|
|
|
}
|
|
|
|
|
2020-10-16 13:51:27 +03:00
|
|
|
let isAllowedWidget = SettingsStore.getValue("allowedWidgets", roomId)[app.eventId];
|
|
|
|
if (isAllowedWidget === undefined) {
|
|
|
|
isAllowedWidget = app.creatorUserId === cli.getUserId();
|
|
|
|
}
|
|
|
|
|
2020-10-12 11:51:49 +03:00
|
|
|
const isLocalWidget = WidgetType.JITSI.matches(app.type);
|
|
|
|
let revokeButton;
|
2020-10-16 13:51:27 +03:00
|
|
|
if (!userWidget && !isLocalWidget && isAllowedWidget) {
|
2020-10-12 11:51:49 +03:00
|
|
|
const onRevokeClick = () => {
|
|
|
|
console.info("Revoking permission for widget to load: " + app.eventId);
|
|
|
|
const current = SettingsStore.getValue("allowedWidgets", roomId);
|
|
|
|
current[app.eventId] = false;
|
2021-01-14 20:30:25 +03:00
|
|
|
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
|
|
|
|
SettingsStore.setValue("allowedWidgets", roomId, level, current).catch(err => {
|
2020-10-12 11:51:49 +03:00
|
|
|
console.error(err);
|
|
|
|
// We don't really need to do anything about this - the user will just hit the button again.
|
|
|
|
});
|
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
|
2020-10-16 13:51:27 +03:00
|
|
|
revokeButton = <IconizedContextMenuOption onClick={onRevokeClick} label={_t("Revoke permissions")} />;
|
2020-10-12 11:51:49 +03:00
|
|
|
}
|
2020-09-30 17:38:35 +03:00
|
|
|
|
2021-01-19 06:26:47 +03:00
|
|
|
const pinnedWidgets = WidgetLayoutStore.instance.getContainerWidgets(room, Container.Top);
|
2020-10-15 17:24:42 +03:00
|
|
|
const widgetIndex = pinnedWidgets.findIndex(widget => widget.id === app.id);
|
|
|
|
|
|
|
|
let moveLeftButton;
|
|
|
|
if (showUnpin && widgetIndex > 0) {
|
|
|
|
const onClick = () => {
|
2021-01-19 06:26:47 +03:00
|
|
|
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, -1);
|
2020-10-15 17:24:42 +03:00
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
|
|
|
|
moveLeftButton = <IconizedContextMenuOption onClick={onClick} label={_t("Move left")} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
let moveRightButton;
|
|
|
|
if (showUnpin && widgetIndex < pinnedWidgets.length - 1) {
|
|
|
|
const onClick = () => {
|
2021-01-19 06:26:47 +03:00
|
|
|
WidgetLayoutStore.instance.moveWithinContainer(room, Container.Top, app, 1);
|
2020-10-15 17:24:42 +03:00
|
|
|
onFinished();
|
|
|
|
};
|
|
|
|
|
|
|
|
moveRightButton = <IconizedContextMenuOption onClick={onClick} label={_t("Move right")} />;
|
|
|
|
}
|
|
|
|
|
2020-09-30 17:38:35 +03:00
|
|
|
return <IconizedContextMenu {...props} chevronFace={ChevronFace.None} onFinished={onFinished}>
|
|
|
|
<IconizedContextMenuOptionList>
|
2021-03-02 18:20:54 +03:00
|
|
|
{ streamAudioStreamButton }
|
2020-10-09 10:42:21 +03:00
|
|
|
{ editButton }
|
2020-10-12 11:51:49 +03:00
|
|
|
{ revokeButton }
|
2020-10-15 17:24:42 +03:00
|
|
|
{ deleteButton }
|
|
|
|
{ snapshotButton }
|
|
|
|
{ moveLeftButton }
|
|
|
|
{ moveRightButton }
|
|
|
|
{ unpinButton }
|
2020-09-30 17:38:35 +03:00
|
|
|
</IconizedContextMenuOptionList>
|
|
|
|
</IconizedContextMenu>;
|
|
|
|
};
|
|
|
|
|
2020-10-12 11:51:49 +03:00
|
|
|
export default WidgetContextMenu;
|