@@ -1508,15 +1500,16 @@ const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemb
break;
}
- return (
-
-
-
+ let previousPhase: RightPanelPhases;
+ // We have no previousPhase for when viewing a UserInfo from a Group or without a Room at this time
+ if (room) {
+ previousPhase = RightPanelPhases.RoomMemberList;
+ }
- { content }
-
-
- );
+ const header =
;
+ return
+ { content }
+ ;
};
UserInfo.propTypes = {
diff --git a/src/components/views/right_panel/WidgetCard.tsx b/src/components/views/right_panel/WidgetCard.tsx
new file mode 100644
index 0000000000..dec30a57f2
--- /dev/null
+++ b/src/components/views/right_panel/WidgetCard.tsx
@@ -0,0 +1,205 @@
+/*
+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, useEffect} from "react";
+import {Room} from "matrix-js-sdk/src/models/room";
+
+import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import BaseCard from "./BaseCard";
+import WidgetUtils from "../../../utils/WidgetUtils";
+import AccessibleButton from "../elements/AccessibleButton";
+import AppTile from "../elements/AppTile";
+import {_t} from "../../../languageHandler";
+import {useWidgets} from "./RoomSummaryCard";
+import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
+import defaultDispatcher from "../../../dispatcher/dispatcher";
+import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
+import {Action} from "../../../dispatcher/actions";
+import WidgetStore from "../../../stores/WidgetStore";
+import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
+import {ChevronFace, ContextMenuButton, useContextMenu} from "../../structures/ContextMenu";
+import IconizedContextMenu, {
+ IconizedContextMenuOption,
+ IconizedContextMenuOptionList,
+} from "../context_menus/IconizedContextMenu";
+import {AppTileActionPayload} from "../../../dispatcher/payloads/AppTileActionPayload";
+import {Capability} from "../../../widgets/WidgetApi";
+import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
+import classNames from "classnames";
+
+interface IProps {
+ room: Room;
+ widgetId: string;
+ onClose(): void;
+}
+
+const WidgetCard: React.FC
= ({ room, widgetId, onClose }) => {
+ const cli = useContext(MatrixClientContext);
+
+ const apps = useWidgets(room);
+ const app = apps.find(a => a.id === widgetId);
+ const isPinned = app && WidgetStore.instance.isPinned(app.id);
+
+ const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
+
+ useEffect(() => {
+ if (!app || isPinned) {
+ // stop showing this card
+ defaultDispatcher.dispatch({
+ action: Action.SetRightPanelPhase,
+ phase: RightPanelPhases.RoomSummary,
+ });
+ }
+ }, [app, isPinned]);
+
+ // Don't render anything as we are about to transition
+ if (!app || isPinned) return null;
+
+ const header =
+ { WidgetUtils.getWidgetName(app) }
+ ;
+
+ const canModify = WidgetUtils.canUserModifyWidgets(room.roomId);
+
+ let contextMenu;
+ if (menuDisplayed) {
+ let snapshotButton;
+ if (ActiveWidgetStore.widgetHasCapability(app.id, Capability.Screenshot)) {
+ const onSnapshotClick = () => {
+ WidgetUtils.snapshotWidget(app);
+ closeMenu();
+ };
+
+ snapshotButton = ;
+ }
+
+ let deleteButton;
+ if (canModify) {
+ const onDeleteClick = () => {
+ defaultDispatcher.dispatch({
+ action: Action.AppTileDelete,
+ widgetId: app.id,
+ });
+ closeMenu();
+ };
+
+ deleteButton = ;
+ }
+
+ const onRevokeClick = () => {
+ defaultDispatcher.dispatch({
+ action: Action.AppTileRevoke,
+ widgetId: app.id,
+ });
+ closeMenu();
+ };
+
+ const rect = handle.current.getBoundingClientRect();
+ contextMenu = (
+
+
+ { snapshotButton }
+ { deleteButton }
+
+
+
+ );
+ }
+
+ const onPinClick = () => {
+ WidgetStore.instance.pinWidget(app.id);
+ };
+
+ const onEditClick = () => {
+ WidgetUtils.editWidget(room, app);
+ };
+
+ let editButton;
+ if (canModify) {
+ editButton =
+ { _t("Edit") }
+ ;
+ }
+
+ const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton";
+
+ let pinButton;
+ if (WidgetStore.instance.canPin(app.id)) {
+ pinButton =
+ { _t("Pin to room") }
+ ;
+ } else {
+ pinButton =
+ { _t("Pin to room") }
+ ;
+ }
+
+ const footer =
+ { editButton }
+ { pinButton }
+
+
+ { contextMenu }
+ ;
+
+ return
+
+ ;
+};
+
+export default WidgetCard;
diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js
index fca46b453f..a67338b9d5 100644
--- a/src/components/views/rooms/AppsDrawer.js
+++ b/src/components/views/rooms/AppsDrawer.js
@@ -17,9 +17,10 @@ limitations under the License.
import React, {useState} from 'react';
import PropTypes from 'prop-types';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
+import classNames from 'classnames';
+import {Resizable} from "re-resizable";
+
import AppTile from '../elements/AppTile';
-import Modal from '../../../Modal';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import * as ScalarMessaging from '../../../ScalarMessaging';
@@ -29,13 +30,9 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
-import classNames from 'classnames';
-import {Resizable} from "re-resizable";
import {useLocalStorageState} from "../../../hooks/useLocalStorageState";
import ResizeNotifier from "../../../utils/ResizeNotifier";
-
-// The maximum number of widgets that can be added in a room
-const MAX_WIDGETS = 2;
+import WidgetStore from "../../../stores/WidgetStore";
export default class AppsDrawer extends React.Component {
static propTypes = {
@@ -61,17 +58,13 @@ export default class AppsDrawer extends React.Component {
componentDidMount() {
ScalarMessaging.startListening();
- MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
- WidgetEchoStore.on('update', this._updateApps);
+ WidgetStore.instance.on(this.props.room.roomId, this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
ScalarMessaging.stopListening();
- if (MatrixClientPeg.get()) {
- MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
- }
- WidgetEchoStore.removeListener('update', this._updateApps);
+ WidgetStore.instance.off(this.props.room.roomId, this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}
@@ -100,28 +93,11 @@ export default class AppsDrawer extends React.Component {
}
};
- onRoomStateEvents = (ev, state) => {
- if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
- return;
- }
- this._updateApps();
- };
-
- _getApps() {
- const widgets = WidgetEchoStore.getEchoedRoomWidgets(
- this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
- );
- return widgets.map((ev) => {
- return WidgetUtils.makeAppConfig(
- ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
- );
- });
- }
+ _getApps = () => WidgetStore.instance.getApps(this.props.room, true);
_updateApps = () => {
- const apps = this._getApps();
this.setState({
- apps: apps,
+ apps: this._getApps(),
});
};
@@ -144,18 +120,6 @@ export default class AppsDrawer extends React.Component {
onClickAddWidget = (e) => {
e.preventDefault();
- // Display a warning dialog if the max number of widgets have already been added to the room
- const apps = this._getApps();
- if (apps && apps.length >= MAX_WIDGETS) {
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
- const errorMsg = `The maximum number of ${MAX_WIDGETS} widgets have already been added to this room.`;
- console.error(errorMsg);
- Modal.createDialog(ErrorDialog, {
- title: _t('Cannot add any more widgets'),
- description: _t('The maximum permitted number of widgets have already been added to this room.'),
- });
- return;
- }
this._launchManageIntegrations();
};
@@ -171,7 +135,7 @@ export default class AppsDrawer extends React.Component {
userId={this.props.userId}
show={this.props.showApps}
creatorUserId={app.creatorUserId}
- widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
+ widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}
whitelistCapabilities={capWhitelist}
/>);
@@ -243,7 +207,7 @@ const PersistentVResizer = ({
resizeNotifier,
children,
}) => {
- const [height, setHeight] = useLocalStorageState("pvr_" + id, 100);
+ const [height, setHeight] = useLocalStorageState("pvr_" + id, 280); // old fixed height was 273px
const [resizing, setResizing] = useState(false);
return ;
+ return
- { settingsButton }
{ pinnedEventsButton }
- { shareRoomButton }
- { manageIntegsButton }
{ forgetButton }
{ searchButton }
;
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 6fb71df30d..26d585b76e 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -94,4 +94,14 @@ export enum Action {
* Trigged after the phase of the right panel is set. Should be used with AfterRightPanelPhaseChangePayload.
*/
AfterRightPanelPhaseChange = "after_right_panel_phase_change",
+
+ /**
+ * Requests that the AppTile deletes the widget. Should be used with the AppTileActionPayload.
+ */
+ AppTileDelete = "appTile_delete",
+
+ /**
+ * Requests that the AppTile revokes the widget. Should be used with the AppTileActionPayload.
+ */
+ AppTileRevoke = "appTile_revoke",
}
diff --git a/src/dispatcher/payloads/AppTileActionPayload.ts b/src/dispatcher/payloads/AppTileActionPayload.ts
new file mode 100644
index 0000000000..3cdb0f8c1f
--- /dev/null
+++ b/src/dispatcher/payloads/AppTileActionPayload.ts
@@ -0,0 +1,23 @@
+/*
+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 { ActionPayload } from "../payloads";
+import { Action } from "../actions";
+
+export interface AppTileActionPayload extends ActionPayload {
+ action: Action.AppTileDelete | Action.AppTileRevoke;
+ widgetId: string;
+}
diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
index 75dea9f3df..4126e8a669 100644
--- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
+++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts
@@ -34,4 +34,5 @@ export interface SetRightPanelPhaseRefireParams {
groupRoomId?: string;
// XXX: The type for event should 'view_3pid_invite' action's payload
event?: any;
+ widgetId?: string;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 47063bdae4..054777fd64 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -387,6 +387,7 @@
"Common names and surnames are easy to guess": "Common names and surnames are easy to guess",
"Straight rows of keys are easy to guess": "Straight rows of keys are easy to guess",
"Short keyboard patterns are easy to guess": "Short keyboard patterns are easy to guess",
+ "Unknown App": "Unknown App",
"Help us improve %(brand)s": "Help us improve %(brand)s",
"Send