mirror of
https://github.com/element-hq/element-web.git
synced 2024-11-30 23:31:28 +03:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
8ac503c3ba
5 changed files with 196 additions and 31 deletions
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 Vector Creations Ltd
|
||||||
|
|
||||||
|
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 sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
import {Group} from 'matrix-js-sdk';
|
||||||
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
|
|
||||||
|
export default class GroupInviteTileContextMenu extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
group: PropTypes.instanceOf(Group).isRequired,
|
||||||
|
/* callback called when the menu is dismissed */
|
||||||
|
onFinished: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this._onClickReject = this._onClickReject.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._unmounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._unmounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClickReject() {
|
||||||
|
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||||
|
Modal.createTrackedDialog('Reject community invite', '', QuestionDialog, {
|
||||||
|
title: _t('Reject invitation'),
|
||||||
|
description: _t('Are you sure you want to reject the invitation?'),
|
||||||
|
onFinished: async (shouldLeave) => {
|
||||||
|
if (!shouldLeave) return;
|
||||||
|
|
||||||
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await GroupStore.leaveGroup(this.props.group.groupId);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error rejecting community invite: ", e);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, {
|
||||||
|
title: _t("Error"),
|
||||||
|
description: _t("Unable to reject invite"),
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the context menu
|
||||||
|
if (this.props.onFinished) {
|
||||||
|
this.props.onFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div>
|
||||||
|
<div className="mx_RoomTileContextMenu_leave" onClick={this._onClickReject} >
|
||||||
|
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_delete.svg" width="15" height="15" />
|
||||||
|
{ _t('Reject') }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -20,6 +20,8 @@ import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'GroupInviteTile',
|
displayName: 'GroupInviteTile',
|
||||||
|
@ -32,6 +34,15 @@ export default React.createClass({
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return ({
|
||||||
|
hover: false,
|
||||||
|
badgeHover: false,
|
||||||
|
menuDisplayed: false,
|
||||||
|
selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onClick: function(e) {
|
onClick: function(e) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_group',
|
action: 'view_group',
|
||||||
|
@ -39,6 +50,55 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMouseEnter: function() {
|
||||||
|
const state = {hover: true};
|
||||||
|
// Only allow non-guests to access the context menu
|
||||||
|
if (!this.context.matrixClient.isGuest()) {
|
||||||
|
state.badgeHover = true;
|
||||||
|
}
|
||||||
|
this.setState(state);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseLeave: function() {
|
||||||
|
this.setState({
|
||||||
|
badgeHover: false,
|
||||||
|
hover: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onBadgeClicked: function(e) {
|
||||||
|
// Prevent the RoomTile onClick event firing as well
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Only allow none guests to access the context menu
|
||||||
|
if (this.context.matrixClient.isGuest()) return;
|
||||||
|
|
||||||
|
// If the badge is clicked, then no longer show tooltip
|
||||||
|
if (this.props.collapsed) {
|
||||||
|
this.setState({ hover: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoomTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
|
||||||
|
const elementRect = e.target.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
const x = elementRect.right + window.pageXOffset + 3;
|
||||||
|
const chevronOffset = 12;
|
||||||
|
let y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset);
|
||||||
|
y = y - (chevronOffset + 8); // where 8 is half the height of the chevron
|
||||||
|
|
||||||
|
ContextualMenu.createMenu(RoomTileContextMenu, {
|
||||||
|
chevronOffset: chevronOffset,
|
||||||
|
left: x,
|
||||||
|
top: y,
|
||||||
|
group: this.props.group,
|
||||||
|
onFinished: () => {
|
||||||
|
this.setState({ menuDisplayed: false });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.setState({ menuDisplayed: true });
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
@ -49,19 +109,37 @@ export default React.createClass({
|
||||||
|
|
||||||
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
||||||
|
|
||||||
const label = <EmojiText
|
const nameClasses = classNames({
|
||||||
element="div"
|
'mx_RoomTile_name': true,
|
||||||
title={this.props.group.groupId}
|
'mx_RoomTile_invite': this.props.isInvite,
|
||||||
className="mx_RoomTile_name mx_RoomTile_badgeShown"
|
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
|
||||||
dir="auto"
|
});
|
||||||
>
|
|
||||||
|
const label = <EmojiText element="div" title={this.props.group.groupId} className={nameClasses} dir="auto">
|
||||||
{ groupName }
|
{ groupName }
|
||||||
</EmojiText>;
|
</EmojiText>;
|
||||||
|
|
||||||
const badge = <div className="mx_RoomSubList_badge mx_RoomSubList_badgeHighlight">!</div>;
|
const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed;
|
||||||
|
const badgeClasses = classNames('mx_RoomSubList_badge mx_RoomSubList_badgeHighlight', {
|
||||||
|
'mx_RoomTile_badgeButton': badgeEllipsis,
|
||||||
|
});
|
||||||
|
|
||||||
|
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
||||||
|
const badge = <div className={badgeClasses} onClick={this.onBadgeClicked}>{ badgeContent }</div>;
|
||||||
|
|
||||||
|
let tooltip;
|
||||||
|
if (this.props.collapsed && this.state.hover) {
|
||||||
|
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
|
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={groupName} dir="auto" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
|
||||||
|
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed,
|
||||||
|
'mx_RoomTile_selected': this.state.selected,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className="mx_RoomTile mx_RoomTile_highlight" onClick={this.onClick}>
|
<AccessibleButton className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
<div className="mx_RoomTile_avatar">
|
<div className="mx_RoomTile_avatar">
|
||||||
{ av }
|
{ av }
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,6 +147,7 @@ export default React.createClass({
|
||||||
{ label }
|
{ label }
|
||||||
{ badge }
|
{ badge }
|
||||||
</div>
|
</div>
|
||||||
|
{ tooltip }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -583,14 +583,18 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_makeGroupInviteTiles() {
|
_makeGroupInviteTiles(filter) {
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
const lcFilter = filter && filter.toLowerCase();
|
||||||
|
|
||||||
const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile');
|
const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile');
|
||||||
for (const group of MatrixClientPeg.get().getGroups()) {
|
for (const group of MatrixClientPeg.get().getGroups()) {
|
||||||
if (group.myMembership !== 'invite') continue;
|
const {groupId, name, myMembership} = group;
|
||||||
|
// filter to only groups in invite state and group_id starts with filter or group name includes it
|
||||||
ret.push(<GroupInviteTile key={group.groupId} group={group} />);
|
if (myMembership !== 'invite') continue;
|
||||||
|
if (lcFilter && !groupId.toLowerCase().startsWith(lcFilter) &&
|
||||||
|
!(name && name.toLowerCase().includes(lcFilter))) continue;
|
||||||
|
ret.push(<GroupInviteTile key={groupId} group={group} collapsed={this.props.collapsed} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -610,7 +614,7 @@ module.exports = React.createClass({
|
||||||
autoshow={true} onScroll={self._whenScrolling} wrappedRef={this._collectGemini}>
|
autoshow={true} onScroll={self._whenScrolling} wrappedRef={this._collectGemini}>
|
||||||
<div className="mx_RoomList">
|
<div className="mx_RoomList">
|
||||||
<RoomSubList list={[]}
|
<RoomSubList list={[]}
|
||||||
extraTiles={this._makeGroupInviteTiles()}
|
extraTiles={this._makeGroupInviteTiles(self.props.searchFilter)}
|
||||||
label={_t('Community Invites')}
|
label={_t('Community Invites')}
|
||||||
editable={false}
|
editable={false}
|
||||||
order="recent"
|
order="recent"
|
||||||
|
|
|
@ -301,7 +301,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
} else if (this.state.hover) {
|
} else if (this.state.hover) {
|
||||||
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} dir="auto" />;
|
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
//var incomingCallBox;
|
//var incomingCallBox;
|
||||||
|
@ -314,7 +314,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let directMessageIndicator;
|
let directMessageIndicator;
|
||||||
if (this._isDirectMessageRoom(this.props.room.roomId)) {
|
if (this._isDirectMessageRoom(this.props.room.roomId)) {
|
||||||
directMessageIndicator = <img src="img/icon_person.svg" className="mx_RoomTile_dm" width="11" height="13" alt="dm" />;
|
directMessageIndicator = <img src="img/icon_person.svg" className="mx_RoomTile_dm" width="11" height="13" alt="dm" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
return <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
|
|
|
@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require('react-dom');
|
import ReactDOM from 'react-dom';
|
||||||
var dis = require('../../../dispatcher');
|
import dis from '../../../dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const MIN_TOOLTIP_HEIGHT = 25;
|
const MIN_TOOLTIP_HEIGHT = 25;
|
||||||
|
@ -77,25 +76,21 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderTooltip: function() {
|
_renderTooltip: function() {
|
||||||
var label = this.props.room ? this.props.room.name : this.props.label;
|
|
||||||
|
|
||||||
// Add the parent's position to the tooltips, so it's correctly
|
// Add the parent's position to the tooltips, so it's correctly
|
||||||
// positioned, also taking into account any window zoom
|
// positioned, also taking into account any window zoom
|
||||||
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
// NOTE: The additional 6 pixels for the left position, is to take account of the
|
||||||
// tooltips chevron
|
// tooltips chevron
|
||||||
var parent = ReactDOM.findDOMNode(this).parentNode;
|
const parent = ReactDOM.findDOMNode(this).parentNode;
|
||||||
var style = {};
|
let style = {};
|
||||||
style = this._updatePosition(style);
|
style = this._updatePosition(style);
|
||||||
style.display = "block";
|
style.display = "block";
|
||||||
|
|
||||||
const tooltipClasses = classNames(
|
const tooltipClasses = classNames("mx_RoomTooltip", this.props.tooltipClassName);
|
||||||
"mx_RoomTooltip", this.props.tooltipClassName,
|
|
||||||
);
|
|
||||||
|
|
||||||
var tooltip = (
|
const tooltip = (
|
||||||
<div className={tooltipClasses} style={style} >
|
<div className={tooltipClasses} style={style}>
|
||||||
<div className="mx_RoomTooltip_chevron"></div>
|
<div className="mx_RoomTooltip_chevron" />
|
||||||
{ label }
|
{ this.props.label }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue