Skip to content

Commit d654778

Browse files
authored
Merge pull request element-hq#1941 from vector-im/wmwragg/room-tag-menu
Wmwragg/room tag menu
2 parents bb18548 + 28343aa commit d654778

21 files changed

+520
-9
lines changed

src/component-index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ module.exports.components['structures.SearchBox'] = require('./components/struct
3636
module.exports.components['structures.ViewSource'] = require('./components/structures/ViewSource');
3737
module.exports.components['views.context_menus.MessageContextMenu'] = require('./components/views/context_menus/MessageContextMenu');
3838
module.exports.components['views.context_menus.NotificationStateContextMenu'] = require('./components/views/context_menus/NotificationStateContextMenu');
39+
module.exports.components['views.context_menus.RoomTagContextMenu'] = require('./components/views/context_menus/RoomTagContextMenu');
3940
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
4041
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
4142
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');

src/components/structures/RoomSubList.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,26 @@ var RoomSubList = React.createClass({
149149
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
150150
},
151151

152+
lexicographicalComparator: function(roomA, roomB) {
153+
return roomA.name > roomB.name ? 1 : -1;
154+
},
155+
156+
// Generates the manual comparator using the given list
152157
manualComparator: function(roomA, roomB) {
153158
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
159+
160+
// Make sure the room tag has an order element, if not set it to be the bottom
154161
var a = roomA.tags[this.props.tagName].order;
155162
var b = roomB.tags[this.props.tagName].order;
156-
return a == b ? this.recentsComparator(roomA, roomB) : ( a > b ? 1 : -1);
163+
164+
// Order undefined room tag orders to the bottom
165+
if (a === undefined && b !== undefined) {
166+
return 1;
167+
} else if (a !== undefined && b === undefined) {
168+
return -1;
169+
}
170+
171+
return a == b ? this.lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1);
157172
},
158173

159174
sortList: function(list, order) {
@@ -164,6 +179,9 @@ var RoomSubList = React.createClass({
164179
if (order === "manual") comparator = this.manualComparator;
165180
if (order === "recent") comparator = this.recentsComparator;
166181

182+
// Fix undefined orders here, and make sure the backend gets updated as well
183+
this._fixUndefinedOrder(list);
184+
167185
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
168186
this.setState({ sortedList: list.sort(comparator) });
169187
},
@@ -312,6 +330,46 @@ var RoomSubList = React.createClass({
312330
this.props.onShowMoreRooms();
313331
},
314332

333+
// Fix any undefined order elements of a room in a manual ordered list
334+
// room.tag[tagname].order
335+
_fixUndefinedOrder: function(list) {
336+
if (this.props.order === "manual") {
337+
var order = 0.0;
338+
var self = this;
339+
340+
// Find the highest (lowest position) order of a room in a manual ordered list
341+
list.forEach(function(room) {
342+
if (room.tags.hasOwnProperty(self.props.tagName)) {
343+
if (order < room.tags[self.props.tagName].order) {
344+
order = room.tags[self.props.tagName].order;
345+
}
346+
}
347+
});
348+
349+
// Fix any undefined order elements of a room in a manual ordered list
350+
// Do this one at a time, as each time a rooms tag data is updated the RoomList
351+
// gets triggered and another list is passed in. Doing it one at a time means that
352+
// we always correctly calculate the highest order for the list - stops multiple
353+
// rooms getting the same order. This is only really relevant for the first time this
354+
// is run with historical room tag data, after that there should only be undefined
355+
// in the list at a time anyway.
356+
for (let i = 0; i < list.length; i++) {
357+
if (list[i].tags[self.props.tagName].order === undefined) {
358+
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
359+
// Do any final stuff here
360+
}).fail(function(err) {
361+
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
362+
Modal.createDialog(ErrorDialog, {
363+
title: "Failed to add tag " + self.props.tagName + " to room",
364+
description: err.toString()
365+
});
366+
});
367+
break;
368+
};
369+
};
370+
}
371+
},
372+
315373
render: function() {
316374
var connectDropTarget = this.props.connectDropTarget;
317375
var RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');

src/components/views/context_menus/NotificationStateContextMenu.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ module.exports = React.createClass({
5757
// Wrapping this in a q promise, as setRoomMutePushRule can return
5858
// a promise or a value
5959
q(cli.setRoomMutePushRule("global", roomId, areNotifsMuted))
60-
.then(function(s) {
60+
.then(function() {
6161
self.setState({areNotifsMuted: areNotifsMuted});
6262

6363
// delay slightly so that the user can see their state change
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
Copyright 2015, 2016 OpenMarket Ltd
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
'use strict';
18+
19+
var q = require("q");
20+
var React = require('react');
21+
var classNames = require('classnames');
22+
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
23+
var dis = require('matrix-react-sdk/lib/dispatcher');
24+
25+
module.exports = React.createClass({
26+
displayName: 'RoomTagContextMenu',
27+
28+
propTypes: {
29+
room: React.PropTypes.object.isRequired,
30+
/* callback called when the menu is dismissed */
31+
onFinished: React.PropTypes.func,
32+
},
33+
34+
getInitialState: function() {
35+
return {
36+
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
37+
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
38+
};
39+
},
40+
41+
_toggleTag: function(tagNameOn, tagNameOff) {
42+
var self = this;
43+
const roomId = this.props.room.roomId;
44+
var cli = MatrixClientPeg.get();
45+
if (!cli.isGuest()) {
46+
q.delay(500).then(function() {
47+
if (tagNameOff !== null && tagNameOff !== undefined) {
48+
cli.deleteRoomTag(roomId, tagNameOff).finally(function() {
49+
// Close the context menu
50+
if (self.props.onFinished) {
51+
self.props.onFinished();
52+
};
53+
}).fail(function(err) {
54+
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
55+
Modal.createDialog(ErrorDialog, {
56+
title: "Failed to remove tag " + tagNameOff + " from room",
57+
description: err.toString()
58+
});
59+
});
60+
}
61+
62+
if (tagNameOn !== null && tagNameOn !== undefined) {
63+
// If the tag ordering meta data is required, it is added by
64+
// the RoomSubList when it sorts its rooms
65+
cli.setRoomTag(roomId, tagNameOn, {}).finally(function() {
66+
// Close the context menu
67+
if (self.props.onFinished) {
68+
self.props.onFinished();
69+
};
70+
}).fail(function(err) {
71+
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
72+
Modal.createDialog(ErrorDialog, {
73+
title: "Failed to add tag " + tagNameOn + " to room",
74+
description: err.toString()
75+
});
76+
});
77+
}
78+
});
79+
}
80+
},
81+
82+
_onClickFavourite: function() {
83+
// Tag room as 'Favourite'
84+
if (!this.state.isFavourite && this.state.isLowPriority) {
85+
this.setState({
86+
isFavourite: true,
87+
isLowPriority: false,
88+
});
89+
this._toggleTag("m.favourite", "m.lowpriority");
90+
} else if (this.state.isFavourite) {
91+
this.setState({isFavourite: false});
92+
this._toggleTag(null, "m.favourite");
93+
} else if (!this.state.isFavourite) {
94+
this.setState({isFavourite: true});
95+
this._toggleTag("m.favourite");
96+
}
97+
},
98+
99+
_onClickLowPriority: function() {
100+
// Tag room as 'Low Priority'
101+
if (!this.state.isLowPriority && this.state.isFavourite) {
102+
this.setState({
103+
isFavourite: false,
104+
isLowPriority: true,
105+
});
106+
this._toggleTag("m.lowpriority", "m.favourite");
107+
} else if (this.state.isLowPriority) {
108+
this.setState({isLowPriority: false});
109+
this._toggleTag(null, "m.lowpriority");
110+
} else if (!this.state.isLowPriority) {
111+
this.setState({isLowPriority: true});
112+
this._toggleTag("m.lowpriority");
113+
}
114+
},
115+
116+
_onClickLeave: function() {
117+
// Leave room
118+
dis.dispatch({
119+
action: 'leave_room',
120+
room_id: this.props.room.roomId,
121+
});
122+
123+
// Close the context menu
124+
if (this.props.onFinished) {
125+
this.props.onFinished();
126+
};
127+
},
128+
129+
render: function() {
130+
var myUserId = MatrixClientPeg.get().credentials.userId;
131+
var myMember = this.props.room.getMember(myUserId);
132+
133+
var favouriteClasses = classNames({
134+
'mx_RoomTagContextMenu_field': true,
135+
'mx_RoomTagContextMenu_fieldSet': this.state.isFavourite,
136+
'mx_RoomTagContextMenu_fieldDisabled': false,
137+
});
138+
139+
var lowPriorityClasses = classNames({
140+
'mx_RoomTagContextMenu_field': true,
141+
'mx_RoomTagContextMenu_fieldSet': this.state.isLowPriority,
142+
'mx_RoomTagContextMenu_fieldDisabled': false,
143+
});
144+
145+
var leaveClasses = classNames({
146+
'mx_RoomTagContextMenu_field': true,
147+
'mx_RoomTagContextMenu_fieldSet': false,
148+
'mx_RoomTagContextMenu_fieldDisabled': false,
149+
});
150+
151+
return (
152+
<div>
153+
<div className={ favouriteClasses } onClick={this._onClickFavourite} >
154+
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_fave.svg" width="15" height="15" />
155+
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
156+
Favourite
157+
</div>
158+
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} >
159+
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_low.svg" width="15" height="15" />
160+
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
161+
Low Priority
162+
</div>
163+
<hr className="mx_RoomTagContextMenu_separator" />
164+
<div className={ leaveClasses } onClick={(myMember && myMember.membership === "join") ? this._onClickLeave : null} >
165+
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_delete.svg" width="15" height="15" />
166+
Leave
167+
</div>
168+
</div>
169+
);
170+
}
171+
});

src/skins/vector/css/common.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ body {
3232
color: #454545;
3333
border: 0px;
3434
margin: 0px;
35+
/* This should render the fonts the same accross browsers */
36+
-webkit-font-smoothing: subpixel-antialiased;
3537
}
3638

3739
div.error {

src/skins/vector/css/matrix-react-sdk/structures/ContextualMenu.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ limitations under the License.
5555
border-bottom: 8px solid transparent;
5656
}
5757

58-
.mx_ContextualMenu_chevron_right:after{
58+
.mx_ContextualMenu_chevron_right:after {
5959
content:'';
6060
width: 0;
6161
height: 0;

src/skins/vector/css/matrix-react-sdk/structures/SearchBox.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ limitations under the License.
1616

1717
.mx_SearchBox {
1818
height: 24px;
19-
margin-left: 18px;
20-
margin-right: 18px;
19+
margin-left: 16px;
20+
margin-right: 16px;
2121
padding-top: 24px;
2222
padding-bottom: 22px;
2323
border-bottom: 1px solid rgba(0, 0, 0, 0.1);

src/skins/vector/css/matrix-react-sdk/views/rooms/RoomHeader.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ limitations under the License.
233233
}
234234

235235
.mx_RoomHeader_button {
236-
margin-left: 8px;
236+
margin-left: 12px;
237237
cursor: pointer;
238238
}
239239

src/skins/vector/css/matrix-react-sdk/views/rooms/RoomTile.css

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,49 @@ limitations under the License.
3232
display: inline-block;
3333
padding-top: 5px;
3434
padding-bottom: 5px;
35-
padding-left: 18px;
35+
padding-left: 16px;
3636
padding-right: 6px;
3737
width: 24px;
3838
height: 24px;
3939
vertical-align: middle;
4040
}
4141

42+
.mx_RoomTile_avatar_container:hover:before,
43+
.mx_RoomTile_avatar_container.mx_RoomTile_avatar_roomTagMenu:before {
44+
display: block;
45+
position: absolute;
46+
content: "";
47+
border-radius: 40px;
48+
background-image: url("img/icons_ellipsis.svg");
49+
background-size: 25px;
50+
left: 15px;
51+
width: 24px;
52+
height: 24px;
53+
z-index: 4;
54+
}
55+
56+
.mx_RoomTile_avatar_container:hover:after,
57+
.mx_RoomTile_avatar_container.mx_RoomTile_avatar_roomTagMenu:after {
58+
display: block;
59+
position: absolute;
60+
content: "";
61+
border-radius: 40px;
62+
background: #4A4A4A;
63+
top: 5px;
64+
width: 24px;
65+
height: 24px;
66+
opacity: 0.6;
67+
z-index: 2;
68+
}
69+
70+
.collapsed .mx_RoomTile_avatar_container:hover:before {
71+
display: none;
72+
}
73+
74+
.collapsed .mx_RoomTile_avatar_container:hover:after {
75+
display: none;
76+
}
77+
4278
.mx_RoomTile_name {
4379
display: inline-block;
4480
position: relative;
@@ -116,13 +152,13 @@ limitations under the License.
116152
}
117153

118154
.mx_RoomTile .mx_RoomTile_badge.mx_RoomTile_badgeButton,
119-
.mx_RoomTile.mx_RoomTile_menu .mx_RoomTile_badge {
155+
.mx_RoomTile.mx_RoomTile_notificationStateMenu .mx_RoomTile_badge {
120156
letter-spacing: 0.1em;
121157
opacity: 1;
122158
}
123159

124160
.mx_RoomTile.mx_RoomTile_noBadges .mx_RoomTile_badge.mx_RoomTile_badgeButton,
125-
.mx_RoomTile.mx_RoomTile_menu.mx_RoomTile_noBadges .mx_RoomTile_badge {
161+
.mx_RoomTile.mx_RoomTile_notificationStateMenu.mx_RoomTile_noBadges .mx_RoomTile_badge {
126162
background-color: rgb(214, 214, 214);
127163
}
128164

0 commit comments

Comments
 (0)