Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Weblate 2018-06-14 16:32:30 +00:00
commit 8ac503c3ba
5 changed files with 196 additions and 31 deletions

View file

@ -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>;
}
}

View file

@ -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>
); );
}, },

View file

@ -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"

View file

@ -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}>

View file

@ -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>
); );