Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/dpsah/6785.3
Conflicts: src/components/structures/RoomView.tsx
This commit is contained in:
commit
a1f1334250
81 changed files with 3611 additions and 805 deletions
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {CSSProperties, useRef, useState} from "react";
|
||||
import React, {CSSProperties, RefObject, useRef, useState} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
|
@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None
|
|||
return menuOptions;
|
||||
};
|
||||
|
||||
export const useContextMenu = () => {
|
||||
const button = useRef(null);
|
||||
export const useContextMenu = (): [boolean, RefObject<HTMLElement>, () => void, () => void, (val: boolean) => void] => {
|
||||
const button = useRef<HTMLElement>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const open = () => {
|
||||
setIsOpen(true);
|
||||
|
|
|
@ -23,6 +23,8 @@ import * as sdk from '../../index';
|
|||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||
import { _t } from '../../languageHandler';
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
|
||||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
|
@ -30,6 +32,7 @@ import { _t } from '../../languageHandler';
|
|||
class FilePanel extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
// This is used to track if a decrypted event was a live event and should be
|
||||
|
@ -188,18 +191,26 @@ class FilePanel extends React.Component {
|
|||
|
||||
render() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||
return <BaseCard
|
||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_RoomView_empty">
|
||||
{ _t("You must <a>register</a> to use this functionality",
|
||||
{},
|
||||
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
|
||||
}
|
||||
</div>
|
||||
</div>;
|
||||
</BaseCard>;
|
||||
} else if (this.noRoom) {
|
||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||
return <BaseCard
|
||||
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
|
||||
</div>;
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||
|
@ -215,7 +226,12 @@ class FilePanel extends React.Component {
|
|||
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
|
||||
// "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
|
||||
return (
|
||||
<div className="mx_FilePanel" role="tabpanel">
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
withoutScrollContainer
|
||||
>
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
manageReadMarkers={false}
|
||||
|
@ -226,13 +242,17 @@ class FilePanel extends React.Component {
|
|||
resizeNotifier={this.props.resizeNotifier}
|
||||
empty={emptyState}
|
||||
/>
|
||||
</div>
|
||||
</BaseCard>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="mx_FilePanel" role="tabpanel">
|
||||
<BaseCard
|
||||
className="mx_FilePanel"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<Loader />
|
||||
</div>
|
||||
</BaseCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,14 +17,21 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { _t } from '../../languageHandler';
|
||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||
import * as sdk from "../../index";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
|
||||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
class NotificationPanel extends React.Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
|
@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {
|
|||
<p>{_t('You have no visible notifications in this room.')}</p>
|
||||
</div>);
|
||||
|
||||
let content;
|
||||
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
||||
if (timelineSet) {
|
||||
return (
|
||||
<div className="mx_NotificationPanel" role="tabpanel">
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
manageReadMarkers={false}
|
||||
timelineSet={timelineSet}
|
||||
showUrlPreview={false}
|
||||
tileShape="notif"
|
||||
empty={emptyState}
|
||||
/>
|
||||
</div>
|
||||
content = (
|
||||
<TimelinePanel
|
||||
manageReadReceipts={false}
|
||||
manageReadMarkers={false}
|
||||
timelineSet={timelineSet}
|
||||
showUrlPreview={false}
|
||||
tileShape="notif"
|
||||
empty={emptyState}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.error("No notifTimelineSet available!");
|
||||
return (
|
||||
<div className="mx_NotificationPanel" role="tabpanel">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
content = <Loader />;
|
||||
}
|
||||
|
||||
return <BaseCard className="mx_NotificationPanel" onClose={this.props.onClose} withoutScrollContainer>
|
||||
{ content }
|
||||
</BaseCard>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,9 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa
|
|||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||
|
||||
export default class RightPanel extends React.Component {
|
||||
static get propTypes() {
|
||||
|
@ -47,10 +50,10 @@ export default class RightPanel extends React.Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
...RightPanelStore.getSharedInstance().roomPanelPhaseParams,
|
||||
phase: this._getPhaseFromProps(),
|
||||
isUserPrivilegedInGroup: null,
|
||||
member: this._getUserForPanel(),
|
||||
verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest,
|
||||
};
|
||||
this.onAction = this.onAction.bind(this);
|
||||
this.onRoomStateMember = this.onRoomStateMember.bind(this);
|
||||
|
@ -102,10 +105,6 @@ export default class RightPanel extends React.Component {
|
|||
}
|
||||
return RightPanelPhases.RoomMemberInfo;
|
||||
} else {
|
||||
if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) {
|
||||
dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList});
|
||||
return RightPanelPhases.RoomMemberList;
|
||||
}
|
||||
return rps.roomPanelPhase;
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +185,7 @@ export default class RightPanel extends React.Component {
|
|||
event: payload.event,
|
||||
verificationRequest: payload.verificationRequest,
|
||||
verificationRequestPromise: payload.verificationRequestPromise,
|
||||
widgetId: payload.widgetId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +213,14 @@ export default class RightPanel extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onClose = () => {
|
||||
// the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ToggleRightPanel,
|
||||
type: this.props.groupId ? "group" : "room",
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const MemberList = sdk.getComponent('rooms.MemberList');
|
||||
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
||||
|
@ -225,36 +233,42 @@ export default class RightPanel extends React.Component {
|
|||
const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
|
||||
|
||||
let panel = <div />;
|
||||
const roomId = this.props.room ? this.props.room.roomId : undefined;
|
||||
|
||||
switch (this.state.phase) {
|
||||
case RightPanelPhases.RoomMemberList:
|
||||
if (this.props.room.roomId) {
|
||||
panel = <MemberList roomId={this.props.room.roomId} key={this.props.room.roomId} />;
|
||||
if (roomId) {
|
||||
panel = <MemberList roomId={roomId} key={roomId} onClose={this.onClose} />;
|
||||
}
|
||||
break;
|
||||
|
||||
case RightPanelPhases.GroupMemberList:
|
||||
if (this.props.groupId) {
|
||||
panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||
}
|
||||
break;
|
||||
|
||||
case RightPanelPhases.GroupRoomList:
|
||||
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.RoomMemberInfo:
|
||||
case RightPanelPhases.EncryptionPanel:
|
||||
panel = <UserInfo
|
||||
user={this.state.member}
|
||||
roomId={this.props.room.roomId}
|
||||
key={this.props.room.roomId || this.state.member.userId}
|
||||
room={this.props.room}
|
||||
key={roomId || this.state.member.userId}
|
||||
onClose={this.onCloseUserInfo}
|
||||
phase={this.state.phase}
|
||||
verificationRequest={this.state.verificationRequest}
|
||||
verificationRequestPromise={this.state.verificationRequestPromise}
|
||||
/>;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.Room3pidMemberInfo:
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.room.roomId} />;
|
||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.GroupMemberInfo:
|
||||
panel = <UserInfo
|
||||
user={this.state.member}
|
||||
|
@ -262,17 +276,28 @@ export default class RightPanel extends React.Component {
|
|||
key={this.state.member.userId}
|
||||
onClose={this.onCloseUserInfo} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.GroupRoomInfo:
|
||||
panel = <GroupRoomInfo
|
||||
groupRoomId={this.state.groupRoomId}
|
||||
groupId={this.props.groupId}
|
||||
key={this.state.groupRoomId} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.NotificationPanel:
|
||||
panel = <NotificationPanel />;
|
||||
panel = <NotificationPanel onClose={this.onClose} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.FilePanel:
|
||||
panel = <FilePanel roomId={this.props.room.roomId} resizeNotifier={this.props.resizeNotifier} />;
|
||||
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.RoomSummary:
|
||||
panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
||||
break;
|
||||
|
||||
case RightPanelPhases.Widget:
|
||||
panel = <WidgetCard room={this.props.room} widgetId={this.state.widgetId} onClose={this.onClose} />;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import {E2EStatus, shieldStatusForRoom} from '../../utils/ShieldUtils';
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import {SettingLevel} from "../../settings/SettingLevel";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
import {IMatrixClientCreds} from "../../MatrixClientPeg";
|
||||
import ScrollPanel from "./ScrollPanel";
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
|
@ -1424,7 +1425,10 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onSettingsClick = () => {
|
||||
dis.dispatch({ action: 'open_room_settings' });
|
||||
dis.dispatch({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomSummary,
|
||||
});
|
||||
};
|
||||
|
||||
private onCancelClick = () => {
|
||||
|
|
|
@ -104,8 +104,8 @@ class TimelinePanel extends React.Component {
|
|||
// shape property to be passed to EventTiles
|
||||
tileShape: PropTypes.string,
|
||||
|
||||
// placeholder text to use if the timeline is empty
|
||||
empty: PropTypes.string,
|
||||
// placeholder to use if the timeline is empty
|
||||
empty: PropTypes.node,
|
||||
|
||||
// whether to show reactions for an event
|
||||
showReactions: PropTypes.bool,
|
||||
|
|
|
@ -124,7 +124,11 @@ export default class LoginComponent extends React.Component {
|
|||
'm.login.cas': () => this._renderSsoStep("cas"),
|
||||
'm.login.sso': () => this._renderSsoStep("sso"),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillMount() {
|
||||
this._initLoginLogic();
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ interface IOptionListProps {
|
|||
}
|
||||
|
||||
interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
|
||||
iconClassName: string;
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
|
||||
|
@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
|
|||
|
||||
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({label, iconClassName, ...props}) => {
|
||||
return <MenuItem {...props} label={label}>
|
||||
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
|
||||
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
|
||||
<span className="mx_IconizedContextMenu_label">{label}</span>
|
||||
</MenuItem>;
|
||||
};
|
||||
|
|
|
@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component {
|
|||
// Callback for when the revoke button is clicked. Required.
|
||||
onRevokeClicked: PropTypes.func.isRequired,
|
||||
|
||||
// Callback for when the unpin button is clicked. Required.
|
||||
onUnpinClicked: PropTypes.func.isRequired,
|
||||
|
||||
// Callback for when the snapshot button is clicked. Button not shown
|
||||
// without a callback.
|
||||
onSnapshotClicked: PropTypes.func,
|
||||
|
@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component {
|
|||
this.proxyClick(this.props.onRevokeClicked);
|
||||
};
|
||||
|
||||
onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked);
|
||||
|
||||
render() {
|
||||
const options = [];
|
||||
|
||||
|
@ -81,6 +86,12 @@ export default class WidgetContextMenu extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
options.push(
|
||||
<MenuItem className="mx_WidgetContextMenu_option" onClick={this.onUnpinClicked} key="unpin">
|
||||
{_t("Unpin")}
|
||||
</MenuItem>,
|
||||
);
|
||||
|
||||
if (this.props.onReloadClicked) {
|
||||
options.push(
|
||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
||||
|
|
|
@ -42,6 +42,8 @@ import {WidgetType} from "../../../widgets/WidgetType";
|
|||
import {Capability} from "../../../widgets/WidgetApi";
|
||||
import {sleep} from "../../../utils/promise";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import WidgetStore from "../../../stores/WidgetStore";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
|
||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||
const ENABLE_REACT_PERF = false;
|
||||
|
@ -310,35 +312,12 @@ export default class AppTile extends React.Component {
|
|||
if (this.props.onEditClick) {
|
||||
this.props.onEditClick();
|
||||
} else {
|
||||
// TODO: Open the right manager for the widget
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
IntegrationManagers.sharedInstance().openAll(
|
||||
this.props.room,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
} else {
|
||||
IntegrationManagers.sharedInstance().getPrimaryManager().open(
|
||||
this.props.room,
|
||||
'type_' + this.props.app.type,
|
||||
this.props.app.id,
|
||||
);
|
||||
}
|
||||
WidgetUtils.editWidget(this.props.room, this.props.app);
|
||||
}
|
||||
}
|
||||
|
||||
_onSnapshotClick() {
|
||||
console.log("Requesting widget snapshot");
|
||||
ActiveWidgetStore.getWidgetMessaging(this.props.app.id).getScreenshot()
|
||||
.catch((err) => {
|
||||
console.error("Failed to get screenshot", err);
|
||||
})
|
||||
.then((screenshot) => {
|
||||
dis.dispatch({
|
||||
action: 'picture_snapshot',
|
||||
file: screenshot,
|
||||
}, true);
|
||||
});
|
||||
WidgetUtils.snapshotWidget(this.props.app);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -419,6 +398,10 @@ export default class AppTile extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onUnpinClicked = () => {
|
||||
WidgetStore.instance.unpinWidget(this.props.app.id);
|
||||
}
|
||||
|
||||
_onRevokeClicked() {
|
||||
console.info("Revoke widget permissions - %s", this.props.app.id);
|
||||
this._revokeWidgetPermission();
|
||||
|
@ -490,12 +473,20 @@ export default class AppTile extends React.Component {
|
|||
if (payload.widgetId === this.props.app.id) {
|
||||
switch (payload.action) {
|
||||
case 'm.sticker':
|
||||
if (this._hasCapability('m.sticker')) {
|
||||
dis.dispatch({action: 'post_sticker_message', data: payload.data});
|
||||
} else {
|
||||
console.warn('Ignoring sticker message. Invalid capability');
|
||||
}
|
||||
break;
|
||||
if (this._hasCapability('m.sticker')) {
|
||||
dis.dispatch({action: 'post_sticker_message', data: payload.data});
|
||||
} else {
|
||||
console.warn('Ignoring sticker message. Invalid capability');
|
||||
}
|
||||
break;
|
||||
|
||||
case Action.AppTileDelete:
|
||||
this._onDeleteClick();
|
||||
break;
|
||||
|
||||
case Action.AppTileRevoke:
|
||||
this._onRevokeClicked();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -626,7 +617,10 @@ export default class AppTile extends React.Component {
|
|||
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
console.log("Replacing Jitsi widget URL with local wrapper");
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true});
|
||||
url = WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: true,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
});
|
||||
url = this._addWurlParams(url);
|
||||
} else {
|
||||
url = this._getSafeUrl(this.state.widgetUrl);
|
||||
|
@ -637,7 +631,10 @@ export default class AppTile extends React.Component {
|
|||
_getPopoutUrl() {
|
||||
if (WidgetType.JITSI.matches(this.props.app.type)) {
|
||||
return this._templatedUrl(
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}),
|
||||
WidgetUtils.getLocalJitsiWrapperUrl({
|
||||
forLocalRender: false,
|
||||
auth: this.props.app.data ? this.props.app.data.auth : null,
|
||||
}),
|
||||
this.props.app.type,
|
||||
);
|
||||
} else {
|
||||
|
@ -833,6 +830,7 @@ export default class AppTile extends React.Component {
|
|||
contextMenu = (
|
||||
<ContextMenu {...aboveLeftOf(elementRect, null)} onFinished={this._closeContextMenu}>
|
||||
<WidgetContextMenu
|
||||
onUnpinClicked={this._onUnpinClicked}
|
||||
onRevokeClicked={this._onRevokeClicked}
|
||||
onEditClicked={showEditButton ? this._onEditClick : undefined}
|
||||
onDeleteClicked={showDeleteButton ? this._onDeleteClick : undefined}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2019 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 from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||
|
||||
export default class ManageIntegsButton extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
onManageIntegrations = (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
const managers = IntegrationManagers.sharedInstance();
|
||||
if (!managers.hasManager()) {
|
||||
managers.openNoManagerDialog();
|
||||
} else {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
managers.openAll(this.props.room);
|
||||
} else {
|
||||
managers.getPrimaryManager().open(this.props.room);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let integrationsButton = <div />;
|
||||
if (IntegrationManagers.sharedInstance().hasManager()) {
|
||||
integrationsButton = (
|
||||
<AccessibleTooltipButton
|
||||
className='mx_RoomHeader_button mx_RoomHeader_manageIntegsButton'
|
||||
title={_t("Manage Integrations")}
|
||||
onClick={this.onManageIntegrations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return integrationsButton;
|
||||
}
|
||||
}
|
||||
|
||||
ManageIntegsButton.propTypes = {
|
||||
room: PropTypes.object.isRequired,
|
||||
};
|
|
@ -76,7 +76,7 @@ export default class PersistentApp extends React.Component {
|
|||
userId={MatrixClientPeg.get().credentials.userId}
|
||||
show={true}
|
||||
creatorUserId={app.creatorUserId}
|
||||
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
||||
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
|
||||
waitForIframeLoad={app.waitForIframeLoad}
|
||||
whitelistCapabilities={capWhitelist}
|
||||
showDelete={false}
|
||||
|
|
|
@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component {
|
|||
customValue: this.props.value,
|
||||
selectValue: 0,
|
||||
};
|
||||
|
||||
this._initStateFromProps(this.props);
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillMount() {
|
||||
this._initStateFromProps(this.props);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
this._initStateFromProps(newProps);
|
||||
|
|
93
src/components/views/right_panel/BaseCard.tsx
Normal file
93
src/components/views/right_panel/BaseCard.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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, {ReactNode} from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
|
||||
interface IProps {
|
||||
header?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
className?: string;
|
||||
withoutScrollContainer?: boolean;
|
||||
previousPhase?: RightPanelPhases;
|
||||
onClose?(): void;
|
||||
}
|
||||
|
||||
interface IGroupProps {
|
||||
className?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Group: React.FC<IGroupProps> = ({ className, title, children }) => {
|
||||
return <div className={classNames("mx_BaseCard_Group", className)}>
|
||||
<h1>{title}</h1>
|
||||
{children}
|
||||
</div>;
|
||||
};
|
||||
|
||||
const BaseCard: React.FC<IProps> = ({
|
||||
onClose,
|
||||
className,
|
||||
header,
|
||||
footer,
|
||||
withoutScrollContainer,
|
||||
previousPhase,
|
||||
children,
|
||||
}) => {
|
||||
let backButton;
|
||||
if (previousPhase) {
|
||||
const onBackClick = () => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: previousPhase,
|
||||
});
|
||||
};
|
||||
backButton = <AccessibleButton className="mx_BaseCard_back" onClick={onBackClick} title={_t("Back")} />;
|
||||
}
|
||||
|
||||
let closeButton;
|
||||
if (onClose) {
|
||||
closeButton = <AccessibleButton className="mx_BaseCard_close" onClick={onClose} title={_t("Close")} />;
|
||||
}
|
||||
|
||||
if (!withoutScrollContainer) {
|
||||
children = <AutoHideScrollbar>
|
||||
{ children }
|
||||
</AutoHideScrollbar>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames("mx_BaseCard", className)}>
|
||||
<div className="mx_BaseCard_header">
|
||||
{ backButton }
|
||||
{ closeButton }
|
||||
{ header }
|
||||
</div>
|
||||
{ children }
|
||||
{ footer && <div className="mx_BaseCard_footer">{ footer }</div> }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseCard;
|
|
@ -96,8 +96,7 @@ export default abstract class HeaderButtons extends React.Component<IProps, ISta
|
|||
public abstract renderButtons(): JSX.Element[];
|
||||
|
||||
public render() {
|
||||
// inline style as this will be swapped around in future commits
|
||||
return <div className="mx_HeaderButtons" role="tablist">
|
||||
return <div className="mx_HeaderButtons">
|
||||
{this.renderButtons()}
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -19,14 +19,18 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import HeaderButton from './HeaderButton';
|
||||
import HeaderButtons, {HeaderKind} from './HeaderButtons';
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {ActionPayload} from "../../../dispatcher/payloads";
|
||||
import RightPanelStore from "../../../stores/RightPanelStore";
|
||||
|
||||
const MEMBER_PHASES = [
|
||||
const ROOM_INFO_PHASES = [
|
||||
RightPanelPhases.RoomSummary,
|
||||
RightPanelPhases.Widget,
|
||||
RightPanelPhases.FilePanel,
|
||||
RightPanelPhases.RoomMemberList,
|
||||
RightPanelPhases.RoomMemberInfo,
|
||||
RightPanelPhases.EncryptionPanel,
|
||||
|
@ -54,22 +58,21 @@ export default class RoomHeaderButtons extends HeaderButtons {
|
|||
}
|
||||
}
|
||||
|
||||
private onMembersClicked = () => {
|
||||
if (this.state.phase === RightPanelPhases.RoomMemberInfo) {
|
||||
// send the active phase to trigger a toggle
|
||||
// XXX: we should pass refireParams here but then it won't collapse as we desire it to
|
||||
this.setPhase(RightPanelPhases.RoomMemberInfo);
|
||||
private onRoomSummaryClicked = () => {
|
||||
// use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close
|
||||
const lastPhase = RightPanelStore.getSharedInstance().roomPanelPhase;
|
||||
if (ROOM_INFO_PHASES.includes(lastPhase)) {
|
||||
if (this.state.phase === lastPhase) {
|
||||
this.setPhase(lastPhase);
|
||||
} else {
|
||||
this.setPhase(lastPhase, RightPanelStore.getSharedInstance().roomPanelPhaseParams);
|
||||
}
|
||||
} else {
|
||||
// This toggles for us, if needed
|
||||
this.setPhase(RightPanelPhases.RoomMemberList);
|
||||
this.setPhase(RightPanelPhases.RoomSummary);
|
||||
}
|
||||
};
|
||||
|
||||
private onFilesClicked = () => {
|
||||
// This toggles for us, if needed
|
||||
this.setPhase(RightPanelPhases.FilePanel);
|
||||
};
|
||||
|
||||
private onNotificationsClicked = () => {
|
||||
// This toggles for us, if needed
|
||||
this.setPhase(RightPanelPhases.NotificationPanel);
|
||||
|
@ -77,24 +80,22 @@ export default class RoomHeaderButtons extends HeaderButtons {
|
|||
|
||||
public renderButtons() {
|
||||
return [
|
||||
<HeaderButton key="membersButton" name="membersButton"
|
||||
title={_t('Members')}
|
||||
isHighlighted={this.isPhase(MEMBER_PHASES)}
|
||||
onClick={this.onMembersClicked}
|
||||
analytics={['Right Panel', 'Member List Button', 'click']}
|
||||
/>,
|
||||
<HeaderButton key="filesButton" name="filesButton"
|
||||
title={_t('Files')}
|
||||
isHighlighted={this.isPhase(RightPanelPhases.FilePanel)}
|
||||
onClick={this.onFilesClicked}
|
||||
analytics={['Right Panel', 'File List Button', 'click']}
|
||||
/>,
|
||||
<HeaderButton key="notifsButton" name="notifsButton"
|
||||
<HeaderButton
|
||||
key="notifsButton"
|
||||
name="notifsButton"
|
||||
title={_t('Notifications')}
|
||||
isHighlighted={this.isPhase(RightPanelPhases.NotificationPanel)}
|
||||
onClick={this.onNotificationsClicked}
|
||||
analytics={['Right Panel', 'Notification List Button', 'click']}
|
||||
/>,
|
||||
<HeaderButton
|
||||
key="roomSummaryButton"
|
||||
name="roomSummaryButton"
|
||||
title={_t('Room Info')}
|
||||
isHighlighted={this.isPhase(ROOM_INFO_PHASES)}
|
||||
onClick={this.onRoomSummaryClicked}
|
||||
analytics={['Right Panel', 'Room Summary Button', 'click']}
|
||||
/>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
243
src/components/views/right_panel/RoomSummaryCard.tsx
Normal file
243
src/components/views/right_panel/RoomSummaryCard.tsx
Normal file
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
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, {useCallback, useState, useEffect, useContext} from "react";
|
||||
import classNames from "classnames";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { useIsEncrypted } from '../../../hooks/useIsEncrypted';
|
||||
import BaseCard, { Group } from "./BaseCard";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||
import Modal from "../../../Modal";
|
||||
import ShareDialog from '../dialogs/ShareDialog';
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
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";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
interface IAppsSectionProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IButtonProps {
|
||||
className: string;
|
||||
onClick(): void;
|
||||
}
|
||||
|
||||
const Button: React.FC<IButtonProps> = ({ children, className, onClick }) => {
|
||||
return <AccessibleButton
|
||||
className={classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{ children }
|
||||
</AccessibleButton>;
|
||||
};
|
||||
|
||||
export const useWidgets = (room: Room) => {
|
||||
const [apps, setApps] = useState<IApp[]>(WidgetStore.instance.getApps(room));
|
||||
|
||||
const updateApps = useCallback(() => {
|
||||
// Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings
|
||||
setApps([...WidgetStore.instance.getApps(room)]);
|
||||
}, [room]);
|
||||
|
||||
useEffect(updateApps, [room]);
|
||||
useEventEmitter(WidgetEchoStore, "update", updateApps);
|
||||
useEventEmitter(WidgetStore.instance, room.roomId, updateApps);
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const apps = useWidgets(room);
|
||||
|
||||
const onManageIntegrations = () => {
|
||||
const managers = IntegrationManagers.sharedInstance();
|
||||
if (!managers.hasManager()) {
|
||||
managers.openNoManagerDialog();
|
||||
} else {
|
||||
if (SettingsStore.getValue("feature_many_integration_managers")) {
|
||||
managers.openAll(room);
|
||||
} else {
|
||||
managers.getPrimaryManager().open(room);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <Group className="mx_RoomSummaryCard_appsGroup" title={_t("Apps")}>
|
||||
{ apps.map(app => {
|
||||
const name = WidgetUtils.getWidgetName(app);
|
||||
const dataTitle = WidgetUtils.getWidgetDataTitle(app);
|
||||
const subtitle = dataTitle && " - " + dataTitle;
|
||||
|
||||
let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")];
|
||||
// heuristics for some better icons until Widgets support their own icons
|
||||
if (app.type.includes("meeting") || app.type.includes("calendar")) {
|
||||
iconUrls = [require("../../../../res/img/element-icons/room/default_cal.svg")];
|
||||
} else if (app.type.includes("pad") || app.type.includes("doc") || app.type.includes("calc")) {
|
||||
iconUrls = [require("../../../../res/img/element-icons/room/default_doc.svg")];
|
||||
} else if (app.type.includes("clock")) {
|
||||
iconUrls = [require("../../../../res/img/element-icons/room/default_clock.svg")];
|
||||
}
|
||||
|
||||
if (app.avatar_url) { // MSC2765
|
||||
iconUrls.unshift(getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop"));
|
||||
}
|
||||
|
||||
const isPinned = WidgetStore.instance.isPinned(app.id);
|
||||
const classes = classNames("mx_RoomSummaryCard_icon_app", {
|
||||
mx_RoomSummaryCard_icon_app_pinned: isPinned,
|
||||
});
|
||||
|
||||
if (isPinned) {
|
||||
const onClick = () => {
|
||||
WidgetStore.instance.unpinWidget(app.id);
|
||||
};
|
||||
|
||||
return <AccessibleTooltipButton
|
||||
key={app.id}
|
||||
className={classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", classes)}
|
||||
onClick={onClick}
|
||||
title={_t("Unpin app")}
|
||||
>
|
||||
<BaseAvatar name={app.id} urls={iconUrls} width={20} height={20} />
|
||||
<span>{name}</span>
|
||||
{ subtitle }
|
||||
</AccessibleTooltipButton>
|
||||
}
|
||||
|
||||
const onOpenWidgetClick = () => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.Widget,
|
||||
refireParams: {
|
||||
widgetId: app.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button key={app.id} className={classes} onClick={onOpenWidgetClick}>
|
||||
<BaseAvatar name={app.id} urls={iconUrls} width={20} height={20} />
|
||||
<span>{name}</span>
|
||||
{ subtitle }
|
||||
</Button>
|
||||
);
|
||||
}) }
|
||||
|
||||
<AccessibleButton kind="link" onClick={onManageIntegrations}>
|
||||
{ apps.length > 0 ? _t("Edit apps, bridges & bots") : _t("Add apps, bridges & bots") }
|
||||
</AccessibleButton>
|
||||
</Group>;
|
||||
};
|
||||
|
||||
const onRoomMembersClick = () => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomFilesClick = () => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.FilePanel,
|
||||
});
|
||||
};
|
||||
|
||||
const onRoomSettingsClick = () => {
|
||||
defaultDispatcher.dispatch({ action: "open_room_settings" });
|
||||
};
|
||||
|
||||
const useMemberCount = (room: Room) => {
|
||||
const [count, setCount] = useState(room.getJoinedMembers().length);
|
||||
useEventEmitter(room.currentState, "RoomState.members", () => {
|
||||
setCount(room.getJoinedMembers().length);
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
const onShareRoomClick = () => {
|
||||
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
|
||||
target: room,
|
||||
});
|
||||
};
|
||||
|
||||
const isRoomEncrypted = useIsEncrypted(cli, room);
|
||||
|
||||
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
|
||||
const header = <React.Fragment>
|
||||
<div className="mx_RoomSummaryCard_avatar" role="presentation">
|
||||
<RoomAvatar room={room} height={54} width={54} viewAvatarOnClick />
|
||||
<TextWithTooltip
|
||||
tooltip={isRoomEncrypted ? _t("Encrypted") : _t("Not encrypted")}
|
||||
class={classNames("mx_RoomSummaryCard_e2ee", {
|
||||
mx_RoomSummaryCard_e2ee_secure: isRoomEncrypted,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 title={room.name}>{ room.name }</h2>
|
||||
<div className="mx_RoomSummaryCard_alias" title={alias}>
|
||||
{ alias }
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
|
||||
const memberCount = useMemberCount(room);
|
||||
|
||||
return <BaseCard header={header} className="mx_RoomSummaryCard" onClose={onClose}>
|
||||
<Group title={_t("About")} className="mx_RoomSummaryCard_aboutGroup">
|
||||
<Button className="mx_RoomSummaryCard_icon_people" onClick={onRoomMembersClick}>
|
||||
{_t("%(count)s people", { count: memberCount })}
|
||||
</Button>
|
||||
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
|
||||
{_t("Show files")}
|
||||
</Button>
|
||||
<Button className="mx_RoomSummaryCard_icon_share" onClick={onShareRoomClick}>
|
||||
{_t("Share room")}
|
||||
</Button>
|
||||
<Button className="mx_RoomSummaryCard_icon_settings" onClick={onRoomSettingsClick}>
|
||||
{_t("Room settings")}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<AppsSection room={room} />
|
||||
</BaseCard>;
|
||||
};
|
||||
|
||||
export default RoomSummaryCard;
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
|||
import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {Group, RoomMember, User} from 'matrix-js-sdk';
|
||||
import {Group, RoomMember, User, Room} from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import * as sdk from '../../../index';
|
||||
|
@ -31,7 +31,6 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
import SdkConfig from '../../../SdkConfig';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {EventTimeline} from "matrix-js-sdk";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import MultiInviter from "../../../utils/MultiInviter";
|
||||
import GroupStore from "../../../stores/GroupStore";
|
||||
|
@ -46,6 +45,7 @@ import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
|
|||
import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification';
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
||||
import BaseCard from "./BaseCard";
|
||||
|
||||
const _disambiguateDevices = (devices) => {
|
||||
const names = Object.create(null);
|
||||
|
@ -451,7 +451,7 @@ const _isMuted = (member, powerLevelContent) => {
|
|||
return member.powerLevel < levelToSend;
|
||||
};
|
||||
|
||||
const useRoomPowerLevels = (cli, room) => {
|
||||
export const useRoomPowerLevels = (cli, room) => {
|
||||
const [powerLevels, setPowerLevels] = useState({});
|
||||
|
||||
const update = useCallback(() => {
|
||||
|
@ -1364,16 +1364,9 @@ const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
|
|||
</React.Fragment>;
|
||||
};
|
||||
|
||||
const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
||||
const UserInfoHeader = ({member, e2eStatus}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
let closeButton;
|
||||
if (onClose) {
|
||||
closeButton = <AccessibleButton className="mx_UserInfo_cancel" onClick={onClose} title={_t('Close')}>
|
||||
<div />
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const onMemberAvatarClick = useCallback(() => {
|
||||
const avatarUrl = member.getMxcAvatarUrl ? member.getMxcAvatarUrl() : member.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
|
@ -1448,7 +1441,6 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
|||
|
||||
const displayName = member.name || member.displayname;
|
||||
return <React.Fragment>
|
||||
{ closeButton }
|
||||
{ avatarElement }
|
||||
|
||||
<div className="mx_UserInfo_container mx_UserInfo_separator">
|
||||
|
@ -1471,11 +1463,9 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
|
|||
</React.Fragment>;
|
||||
};
|
||||
|
||||
const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
|
||||
const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
|
||||
// Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info
|
||||
const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]);
|
||||
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
|
||||
const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]);
|
||||
|
||||
|
@ -1510,15 +1500,16 @@ const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMe
|
|||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.join(" ")} role="tabpanel">
|
||||
<AutoHideScrollbar className="mx_UserInfo_scrollContainer">
|
||||
<UserInfoHeader member={member} e2eStatus={e2eStatus} onClose={onClose} />
|
||||
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 }
|
||||
</AutoHideScrollbar>
|
||||
</div>
|
||||
);
|
||||
const header = <UserInfoHeader member={member} e2eStatus={e2eStatus} onClose={onClose} />;
|
||||
return <BaseCard className={classes.join(" ")} header={header} onClose={onClose} previousPhase={previousPhase}>
|
||||
{ content }
|
||||
</BaseCard>;
|
||||
};
|
||||
|
||||
UserInfo.propTypes = {
|
||||
|
@ -1529,7 +1520,7 @@ UserInfo.propTypes = {
|
|||
]).isRequired,
|
||||
group: PropTypes.instanceOf(Group),
|
||||
groupId: PropTypes.string,
|
||||
roomId: PropTypes.string,
|
||||
room: PropTypes.instanceOf(Room),
|
||||
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
|
205
src/components/views/right_panel/WidgetCard.tsx
Normal file
205
src/components/views/right_panel/WidgetCard.tsx
Normal file
|
@ -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<IProps> = ({ 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<SetRightPanelPhasePayload>({
|
||||
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 = <React.Fragment>
|
||||
<h2>{ WidgetUtils.getWidgetName(app) }</h2>
|
||||
</React.Fragment>;
|
||||
|
||||
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 = <IconizedContextMenuOption onClick={onSnapshotClick} label={_t("Take a picture")} />;
|
||||
}
|
||||
|
||||
let deleteButton;
|
||||
if (canModify) {
|
||||
const onDeleteClick = () => {
|
||||
defaultDispatcher.dispatch<AppTileActionPayload>({
|
||||
action: Action.AppTileDelete,
|
||||
widgetId: app.id,
|
||||
});
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
deleteButton = <IconizedContextMenuOption onClick={onDeleteClick} label={_t("Remove for everyone")} />;
|
||||
}
|
||||
|
||||
const onRevokeClick = () => {
|
||||
defaultDispatcher.dispatch<AppTileActionPayload>({
|
||||
action: Action.AppTileRevoke,
|
||||
widgetId: app.id,
|
||||
});
|
||||
closeMenu();
|
||||
};
|
||||
|
||||
const rect = handle.current.getBoundingClientRect();
|
||||
contextMenu = (
|
||||
<IconizedContextMenu
|
||||
chevronFace={ChevronFace.None}
|
||||
right={window.innerWidth - rect.right}
|
||||
bottom={window.innerHeight - rect.top}
|
||||
onFinished={closeMenu}
|
||||
>
|
||||
<IconizedContextMenuOptionList>
|
||||
{ snapshotButton }
|
||||
{ deleteButton }
|
||||
<IconizedContextMenuOption onClick={onRevokeClick} label={_t("Remove for me")} />
|
||||
</IconizedContextMenuOptionList>
|
||||
</IconizedContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
const onPinClick = () => {
|
||||
WidgetStore.instance.pinWidget(app.id);
|
||||
};
|
||||
|
||||
const onEditClick = () => {
|
||||
WidgetUtils.editWidget(room, app);
|
||||
};
|
||||
|
||||
let editButton;
|
||||
if (canModify) {
|
||||
editButton = <AccessibleButton kind="secondary" onClick={onEditClick}>
|
||||
{ _t("Edit") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const pinButtonClasses = canModify ? "" : "mx_WidgetCard_widePinButton";
|
||||
|
||||
let pinButton;
|
||||
if (WidgetStore.instance.canPin(app.id)) {
|
||||
pinButton = <AccessibleButton
|
||||
kind="secondary"
|
||||
onClick={onPinClick}
|
||||
className={pinButtonClasses}
|
||||
>
|
||||
{ _t("Pin to room") }
|
||||
</AccessibleButton>;
|
||||
} else {
|
||||
pinButton = <AccessibleTooltipButton
|
||||
title={_t("You can only pin 2 apps at a time")}
|
||||
tooltipClassName="mx_WidgetCard_maxPinnedTooltip"
|
||||
kind="secondary"
|
||||
className={pinButtonClasses}
|
||||
disabled
|
||||
>
|
||||
{ _t("Pin to room") }
|
||||
</AccessibleTooltipButton>;
|
||||
}
|
||||
|
||||
const footer = <React.Fragment>
|
||||
{ editButton }
|
||||
{ pinButton }
|
||||
<ContextMenuButton
|
||||
kind="secondary"
|
||||
className="mx_WidgetCard_optionsButton"
|
||||
inputRef={handle}
|
||||
onClick={openMenu}
|
||||
isExpanded={menuDisplayed}
|
||||
label={_t("Options")}
|
||||
/>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
|
||||
return <BaseCard
|
||||
header={header}
|
||||
footer={footer}
|
||||
className={classNames("mx_WidgetCard", {
|
||||
mx_WidgetCard_noEdit: !canModify,
|
||||
})}
|
||||
onClose={onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
withoutScrollContainer
|
||||
>
|
||||
<AppTile
|
||||
app={app}
|
||||
fullWidth
|
||||
show
|
||||
showMenubar={false}
|
||||
room={room}
|
||||
userId={cli.getUserId()}
|
||||
creatorUserId={app.creatorUserId}
|
||||
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
|
||||
waitForIframeLoad={app.waitForIframeLoad}
|
||||
whitelistCapabilities={WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, room.roomId)}
|
||||
/>
|
||||
</BaseCard>;
|
||||
};
|
||||
|
||||
export default WidgetCard;
|
|
@ -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 <Resizable
|
||||
|
|
|
@ -20,13 +20,14 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import {isValid3pidInvite} from "../../../RoomInvite";
|
||||
import rate_limited_func from "../../../ratelimitedfunc";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../index";
|
||||
import CallHandler from "../../../CallHandler";
|
||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||
import BaseCard from "../right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
|
||||
|
||||
const INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||
const INITIAL_LOAD_NUM_INVITED = 5;
|
||||
|
@ -438,7 +439,13 @@ export default class MemberList extends React.Component {
|
|||
render() {
|
||||
if (this.state.loading) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <div className="mx_MemberList"><Spinner /></div>;
|
||||
return <BaseCard
|
||||
className="mx_MemberList"
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<Spinner />
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||
|
@ -485,25 +492,29 @@ export default class MemberList extends React.Component {
|
|||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_MemberList" role="tabpanel">
|
||||
{ inviteButton }
|
||||
<AutoHideScrollbar>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
|
||||
createOverflowElement={this._createOverflowTileJoined}
|
||||
getChildren={this._getChildrenJoined}
|
||||
getChildCount={this._getChildCountJoined} />
|
||||
{ invitedHeader }
|
||||
{ invitedSection }
|
||||
</div>
|
||||
</AutoHideScrollbar>
|
||||
|
||||
<SearchBox className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t('Filter room members') }
|
||||
onSearch={ this.onSearchQueryChanged } />
|
||||
</div>
|
||||
const footer = (
|
||||
<SearchBox
|
||||
className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
||||
placeholder={ _t('Filter room members') }
|
||||
onSearch={ this.onSearchQueryChanged } />
|
||||
);
|
||||
|
||||
return <BaseCard
|
||||
className="mx_MemberList"
|
||||
header={inviteButton}
|
||||
footer={footer}
|
||||
onClose={this.props.onClose}
|
||||
previousPhase={RightPanelPhases.RoomSummary}
|
||||
>
|
||||
<div className="mx_MemberList_wrapper">
|
||||
<TruncatedList className="mx_MemberList_section mx_MemberList_joined" truncateAt={this.state.truncateAtJoined}
|
||||
createOverflowElement={this._createOverflowTileJoined}
|
||||
getChildren={this._getChildrenJoined}
|
||||
getChildCount={this._getChildCountJoined} />
|
||||
{ invitedHeader }
|
||||
{ invitedSection }
|
||||
</div>
|
||||
</BaseCard>;
|
||||
}
|
||||
|
||||
onInviteButtonClick = () => {
|
||||
|
|
|
@ -18,14 +18,11 @@ limitations under the License.
|
|||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import Modal from "../../../Modal";
|
||||
import RateLimitedFunc from '../../../ratelimitedfunc';
|
||||
|
||||
import { linkifyElement } from '../../../HtmlUtils';
|
||||
import ManageIntegsButton from '../elements/ManageIntegsButton';
|
||||
import {CancelButton} from './SimpleRoomHeader';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
|
||||
|
@ -114,13 +111,6 @@ export default class RoomHeader extends React.Component {
|
|||
this.forceUpdate();
|
||||
};
|
||||
|
||||
onShareRoomClick = (ev) => {
|
||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
||||
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
|
||||
target: this.props.room,
|
||||
});
|
||||
};
|
||||
|
||||
_hasUnreadPins() {
|
||||
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
|
||||
if (!currentPinEvent) return false;
|
||||
|
@ -150,7 +140,6 @@ export default class RoomHeader extends React.Component {
|
|||
render() {
|
||||
let searchStatus = null;
|
||||
let cancelButton = null;
|
||||
let settingsButton = null;
|
||||
let pinnedEventsButton = null;
|
||||
|
||||
if (this.props.onCancelClick) {
|
||||
|
@ -214,14 +203,6 @@ export default class RoomHeader extends React.Component {
|
|||
/>;
|
||||
}
|
||||
|
||||
if (this.props.onSettingsClick) {
|
||||
settingsButton =
|
||||
<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_settingsButton"
|
||||
onClick={this.props.onSettingsClick}
|
||||
title={_t("Settings")} />;
|
||||
}
|
||||
|
||||
if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) {
|
||||
let pinsIndicator = null;
|
||||
if (this._hasUnreadPins()) {
|
||||
|
@ -258,26 +239,9 @@ export default class RoomHeader extends React.Component {
|
|||
title={_t("Search")} />;
|
||||
}
|
||||
|
||||
let shareRoomButton;
|
||||
if (this.props.inRoom) {
|
||||
shareRoomButton =
|
||||
<AccessibleTooltipButton
|
||||
className="mx_RoomHeader_button mx_RoomHeader_shareButton"
|
||||
onClick={this.onShareRoomClick}
|
||||
title={_t('Share room')} />;
|
||||
}
|
||||
|
||||
let manageIntegsButton;
|
||||
if (this.props.room && this.props.room.roomId && this.props.inRoom) {
|
||||
manageIntegsButton = <ManageIntegsButton room={this.props.room} />;
|
||||
}
|
||||
|
||||
const rightRow =
|
||||
<div className="mx_RoomHeader_buttons">
|
||||
{ settingsButton }
|
||||
{ pinnedEventsButton }
|
||||
{ shareRoomButton }
|
||||
{ manageIntegsButton }
|
||||
{ forgetButton }
|
||||
{ searchButton }
|
||||
</div>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue