Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1950 from matrix-org/t3chguy/group_invite_context…
Browse files Browse the repository at this point in the history
…menu

make RoomTooltip generic and add ContextMenu&Tooltip to GroupInviteTile
  • Loading branch information
lukebarnard1 authored Jun 14, 2018
2 parents 5077e9e + 3b141d7 commit e81edd9
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 31 deletions.
87 changes: 87 additions & 0 deletions src/components/views/context_menus/GroupInviteTileContextMenu.js
Original file line number Diff line number Diff line change
@@ -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>;
}
}
97 changes: 88 additions & 9 deletions src/components/views/groups/GroupInviteTile.js
Original file line number Diff line number Diff line change
@@ -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");
you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,8 @@ import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
import * as ContextualMenu from "../../structures/ContextualMenu";
import classNames from 'classnames';

export default React.createClass({
displayName: 'GroupInviteTile',
Expand All @@ -32,13 +34,71 @@ export default React.createClass({
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) {
dis.dispatch({
action: 'view_group',
group_id: this.props.group.groupId,
});
},

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() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EmojiText = sdk.getComponent('elements.EmojiText');
Expand All @@ -49,26 +109,45 @@ export default React.createClass({

const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;

const label = <EmojiText
element="div"
title={this.props.group.groupId}
className="mx_RoomTile_name mx_RoomTile_badgeShown"
dir="auto"
>
const nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite,
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
});

const label = <EmojiText element="div" title={this.props.group.groupId} className={nameClasses} dir="auto">
{ groupName }
</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 (
<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">
{ av }
</div>
<div className="mx_RoomTile_nameContainer">
{ label }
{ badge }
</div>
{ tooltip }
</AccessibleButton>
);
},
Expand Down
14 changes: 9 additions & 5 deletions src/components/views/rooms/RoomList.js
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,18 @@ module.exports = React.createClass({
}
},

_makeGroupInviteTiles() {
_makeGroupInviteTiles(filter) {
const ret = [];
const lcFilter = filter && filter.toLowerCase();

const GroupInviteTile = sdk.getComponent('groups.GroupInviteTile');
for (const group of MatrixClientPeg.get().getGroups()) {
if (group.myMembership !== 'invite') continue;

ret.push(<GroupInviteTile key={group.groupId} group={group} />);
const {groupId, name, myMembership} = group;
// filter to only groups in invite state and group_id starts with filter or group name includes it
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;
Expand All @@ -610,7 +614,7 @@ module.exports = React.createClass({
autoshow={true} onScroll={self._whenScrolling} wrappedRef={this._collectGemini}>
<div className="mx_RoomList">
<RoomSubList list={[]}
extraTiles={this._makeGroupInviteTiles()}
extraTiles={this._makeGroupInviteTiles(self.props.searchFilter)}
label={_t('Community Invites')}
editable={false}
order="recent"
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/rooms/RoomTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ module.exports = React.createClass({
}
} else if (this.state.hover) {
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;
Expand All @@ -314,7 +314,7 @@ module.exports = React.createClass({

let directMessageIndicator;
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}>
Expand Down
25 changes: 10 additions & 15 deletions src/components/views/rooms/RoomTooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require('react');
var ReactDOM = require('react-dom');
var dis = require('../../../dispatcher');
import React from 'react';
import ReactDOM from 'react-dom';
import dis from '../../../dispatcher';
import classNames from 'classnames';

const MIN_TOOLTIP_HEIGHT = 25;
Expand Down Expand Up @@ -77,25 +76,21 @@ module.exports = React.createClass({
},

_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
// positioned, also taking into account any window zoom
// NOTE: The additional 6 pixels for the left position, is to take account of the
// tooltips chevron
var parent = ReactDOM.findDOMNode(this).parentNode;
var style = {};
const parent = ReactDOM.findDOMNode(this).parentNode;
let style = {};
style = this._updatePosition(style);
style.display = "block";

const tooltipClasses = classNames(
"mx_RoomTooltip", this.props.tooltipClassName,
);
const tooltipClasses = classNames("mx_RoomTooltip", this.props.tooltipClassName);

var tooltip = (
<div className={tooltipClasses} style={style} >
<div className="mx_RoomTooltip_chevron"></div>
{ label }
const tooltip = (
<div className={tooltipClasses} style={style}>
<div className="mx_RoomTooltip_chevron" />
{ this.props.label }
</div>
);

Expand Down

0 comments on commit e81edd9

Please sign in to comment.