Merge branch 'develop' into luke/feature-tag-panel-tile-context-menu

This commit is contained in:
Luke Barnard 2018-02-13 17:00:19 +00:00
commit 11b0cc8211
6 changed files with 139 additions and 315 deletions

View File

@ -167,7 +167,6 @@ module.exports = React.createClass({
const StartChatButton = sdk.getComponent('elements.StartChatButton'); const StartChatButton = sdk.getComponent('elements.StartChatButton');
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
const GroupsButton = sdk.getComponent('elements.GroupsButton');
const SettingsButton = sdk.getComponent('elements.SettingsButton'); const SettingsButton = sdk.getComponent('elements.SettingsButton');
return ( return (
@ -183,7 +182,6 @@ module.exports = React.createClass({
<div ref={this._collectCreateRoomButton}> <div ref={this._collectCreateRoomButton}>
<CreateRoomButton tooltip={true} /> <CreateRoomButton tooltip={true} />
</div> </div>
<GroupsButton tooltip={true} />
<span className="mx_BottomLeftMenu_settings"> <span className="mx_BottomLeftMenu_settings">
<SettingsButton tooltip={true} /> <SettingsButton tooltip={true} />
</span> </span>

View File

@ -17,22 +17,31 @@ limitations under the License.
'use strict'; 'use strict';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { DragDropContext } from 'react-beautiful-dnd';
import { MatrixClient } from 'matrix-js-sdk';
import { KeyCode } from 'matrix-react-sdk/lib/Keyboard'; import { KeyCode } from 'matrix-react-sdk/lib/Keyboard';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import dis from 'matrix-react-sdk/lib/dispatcher'; import dis from 'matrix-react-sdk/lib/dispatcher';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import CallHandler from 'matrix-react-sdk/lib/CallHandler';
import AccessibleButton from 'matrix-react-sdk/lib/components/views/elements/AccessibleButton';
import VectorConferenceHandler from '../../VectorConferenceHandler'; import VectorConferenceHandler from '../../VectorConferenceHandler';
import SettingsStore from 'matrix-react-sdk/lib/settings/SettingsStore';
import TagOrderActions from 'matrix-react-sdk/lib/actions/TagOrderActions';
import RoomListActions from 'matrix-react-sdk/lib/actions/RoomListActions';
var LeftPanel = React.createClass({ var LeftPanel = React.createClass({
displayName: 'LeftPanel', displayName: 'LeftPanel',
// NB. If you add props, don't forget to update // NB. If you add props, don't forget to update
// shouldComponentUpdate! // shouldComponentUpdate!
propTypes: { propTypes: {
collapsed: React.PropTypes.bool.isRequired, collapsed: PropTypes.bool.isRequired,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
}, },
getInitialState: function() { getInitialState: function() {
@ -161,13 +170,59 @@ var LeftPanel = React.createClass({
this.setState({ searchFilter: term }); this.setState({ searchFilter: term });
}, },
onDragEnd: function(result) {
// Dragged to an invalid destination, not onto a droppable
if (!result.destination) {
return;
}
const dest = result.destination.droppableId;
if (dest === 'tag-panel-droppable') {
// Dispatch synchronously so that the TagPanel receives an
// optimistic update from TagOrderStore before the previous
// state is shown.
dis.dispatch(TagOrderActions.moveTag(
this.context.matrixClient,
result.draggableId,
result.destination.index,
), true);
} else {
this.onRoomTileEndDrag(result);
}
},
onRoomTileEndDrag: function(result) {
let newTag = result.destination.droppableId.split('_')[1];
let prevTag = result.source.droppableId.split('_')[1];
if (newTag === 'undefined') newTag = undefined;
if (prevTag === 'undefined') prevTag = undefined;
const roomId = result.draggableId.split('_')[1];
const oldIndex = result.source.index;
const newIndex = result.destination.index;
dis.dispatch(RoomListActions.tagRoom(
this.context.matrixClient,
this.context.matrixClient.getRoom(roomId),
prevTag, newTag,
oldIndex, newIndex,
), true);
},
collectRoomList: function(ref) {
this._roomList = ref;
},
render: function() { render: function() {
const RoomList = sdk.getComponent('rooms.RoomList'); const RoomList = sdk.getComponent('rooms.RoomList');
const TagPanel = sdk.getComponent('structures.TagPanel');
const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu'); const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
const CallPreview = sdk.getComponent('voip.CallPreview'); const CallPreview = sdk.getComponent('voip.CallPreview');
let topBox; let topBox;
if (MatrixClientPeg.get().isGuest()) { if (this.context.matrixClient.isGuest()) {
const LoginBox = sdk.getComponent('structures.LoginBox'); const LoginBox = sdk.getComponent('structures.LoginBox');
topBox = <LoginBox collapsed={ this.props.collapsed }/>; topBox = <LoginBox collapsed={ this.props.collapsed }/>;
} else { } else {
@ -184,15 +239,21 @@ var LeftPanel = React.createClass({
); );
return ( return (
<aside className={classes} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }> <DragDropContext onDragEnd={this.onDragEnd}>
{ topBox } <div className="mx_LeftPanel_container">
<CallPreview ConferenceHandler={VectorConferenceHandler} /> { SettingsStore.isFeatureEnabled("feature_tag_panel") ? <TagPanel /> : <div /> }
<RoomList <aside className={classes} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
collapsed={this.props.collapsed} { topBox }
searchFilter={this.state.searchFilter} <CallPreview ConferenceHandler={VectorConferenceHandler} />
ConferenceHandler={VectorConferenceHandler} /> <RoomList
<BottomLeftMenu collapsed={this.props.collapsed}/> ref={this.collectRoomList}
</aside> collapsed={this.props.collapsed}
searchFilter={this.state.searchFilter}
ConferenceHandler={VectorConferenceHandler} />
<BottomLeftMenu collapsed={this.props.collapsed}/>
</aside>
</div>
</DragDropContext>
); );
} }
}); });

View File

@ -38,31 +38,6 @@ var debug = false;
const TRUNCATE_AT = 10; const TRUNCATE_AT = 10;
var roomListTarget = {
canDrop: function() {
return true;
},
drop: function(props, monitor, component) {
if (debug) console.log("dropped on sublist")
},
hover: function(props, monitor, component) {
var item = monitor.getItem();
if (component.state.sortedList.length == 0 && props.editable) {
if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
if (item.targetList !== component) {
item.targetList.removeRoomTile(item.room);
item.targetList = component;
}
component.moveRoomTile(item.room, 0);
}
},
};
var RoomSubList = React.createClass({ var RoomSubList = React.createClass({
displayName: 'RoomSubList', displayName: 'RoomSubList',
@ -110,13 +85,17 @@ var RoomSubList = React.createClass({
}, },
componentWillMount: function() { componentWillMount: function() {
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order); this.setState({
sortedList: this.applySearchFilter(this.props.list, this.props.searchFilter),
});
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
// order the room list appropriately before we re-render // order the room list appropriately before we re-render
//if (debug) console.log("received new props, list = " + newProps.list); //if (debug) console.log("received new props, list = " + newProps.list);
this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order); this.setState({
sortedList: this.applySearchFilter(newProps.list, newProps.searchFilter),
});
}, },
applySearchFilter: function(list, filter) { applySearchFilter: function(list, filter) {
@ -164,71 +143,6 @@ var RoomSubList = React.createClass({
}); });
}, },
tsOfNewestEvent: function(room) {
for (var i = room.timeline.length - 1; i >= 0; --i) {
var ev = room.timeline[i];
if (ev.getTs() &&
(Unread.eventTriggersUnreadCount(ev) ||
(ev.getSender() === MatrixClientPeg.get().credentials.userId))
) {
return ev.getTs();
}
}
// we might only have events that don't trigger the unread indicator,
// in which case use the oldest event even if normally it wouldn't count.
// This is better than just assuming the last event was forever ago.
if (room.timeline.length && room.timeline[0].getTs()) {
return room.timeline[0].getTs();
} else {
return Number.MAX_SAFE_INTEGER;
}
},
// TODO: factor the comparators back out into a generic comparator
// so that view_prev_room and view_next_room can do the right thing
recentsComparator: function(roomA, roomB) {
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
},
lexicographicalComparator: function(roomA, roomB) {
return roomA.name > roomB.name ? 1 : -1;
},
// Generates the manual comparator using the given list
manualComparator: function(roomA, roomB) {
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
// Make sure the room tag has an order element, if not set it to be the bottom
var a = roomA.tags[this.props.tagName].order;
var b = roomB.tags[this.props.tagName].order;
// Order undefined room tag orders to the bottom
if (a === undefined && b !== undefined) {
return 1;
} else if (a !== undefined && b === undefined) {
return -1;
}
return a == b ? this.lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1);
},
sortList: function(list, order) {
if (list === undefined) list = this.state.sortedList;
if (order === undefined) order = this.props.order;
var comparator;
list = list || [];
if (order === "manual") comparator = this.manualComparator;
if (order === "recent") comparator = this.recentsComparator;
// Fix undefined orders here, and make sure the backend gets updated as well
this._fixUndefinedOrder(list);
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
this.setState({ sortedList: list.sort(comparator) });
},
_shouldShowNotifBadge: function(roomNotifState) { _shouldShowNotifBadge: function(roomNotifState) {
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
return showBadgeInStates.indexOf(roomNotifState) > -1; return showBadgeInStates.indexOf(roomNotifState) > -1;
@ -279,98 +193,6 @@ var RoomSubList = React.createClass({
this.setState(this.state); this.setState(this.state);
}, },
moveRoomTile: function(room, atIndex) {
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
rooms.splice(found.index, 1);
rooms.splice(atIndex, 0, found.room);
}
else {
if (debug) console.log("Adding at index " + atIndex);
rooms.splice(atIndex, 0, room);
}
this.setState({ sortedList: rooms });
// console.log("moveRoomTile after: " + JSON.stringify(this.state.rooms));
},
// XXX: this isn't invoked via a property method but indirectly via
// the roomList property method. Unsure how evil this is.
removeRoomTile: function(room) {
if (debug) console.log("remove room " + room.roomId);
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
rooms.splice(found.index, 1);
}
else {
console.warn("Can't remove room " + room.roomId + " - can't find it");
}
this.setState({ sortedList: rooms });
},
findRoomTile: function(room) {
var index = this.state.sortedList.indexOf(room);
if (index >= 0) {
// console.log("found: room: " + room.roomId + " with index " + index);
}
else {
if (debug) console.log("didn't find room");
room = null;
}
return ({
room: room,
index: index,
});
},
calcManualOrderTagData: function(index) {
// we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
// for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
let orderA = 0.0; // by default we're next to the beginning of the list
if (index > 0) {
const prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
if (!prevTag) {
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.");
} else if (prevTag.order === undefined) {
console.error("Previous room in sublist has no ordering metadata. This should never happen.");
} else {
orderA = prevTag.order;
}
}
let orderB = 1.0; // by default we're next to the end of the list too
if (index < this.state.sortedList.length - 1) {
const nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
if (!nextTag) {
console.error("Next room in sublist is not tagged to be in this list. This should never happen.");
} else if (nextTag.order === undefined) {
console.error("Next room in sublist has no ordering metadata. This should never happen.");
} else {
orderB = nextTag.order;
}
}
const order = (orderA + orderB) / 2.0;
if (order === orderA || order === orderB) {
console.error("Cannot describe new list position. This should be incredibly unlikely.");
this.state.sortedList.forEach((room, index) => {
MatrixClientPeg.get().setRoomTag(
room.roomId, this.props.tagName,
{order: index / this.state.sortedList.length},
);
});
return index / this.state.sortedList.length;
}
return order;
},
makeRoomTiles: function() { makeRoomTiles: function() {
var self = this; var self = this;
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile"); var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
@ -497,47 +319,6 @@ var RoomSubList = React.createClass({
this.props.onHeaderClick(false); this.props.onHeaderClick(false);
}, },
// Fix any undefined order elements of a room in a manual ordered list
// room.tag[tagname].order
_fixUndefinedOrder: function(list) {
if (this.props.order === "manual") {
var order = 0.0;
var self = this;
// Find the highest (lowest position) order of a room in a manual ordered list
list.forEach(function(room) {
if (room.tags.hasOwnProperty(self.props.tagName)) {
if (order < room.tags[self.props.tagName].order) {
order = room.tags[self.props.tagName].order;
}
}
});
// Fix any undefined order elements of a room in a manual ordered list
// Do this one at a time, as each time a rooms tag data is updated the RoomList
// gets triggered and another list is passed in. Doing it one at a time means that
// we always correctly calculate the highest order for the list - stops multiple
// rooms getting the same order. This is only really relevant for the first time this
// is run with historical room tag data, after that there should only be undefined
// in the list at a time anyway.
for (let i = 0; i < list.length; i++) {
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
// Do any final stuff here
}).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + self.props.tagName + " to room" + err);
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
title: _t('Failed to add tag %(tagName)s to room', {tagName: self.props.tagName}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
break;
};
};
}
},
render: function() { render: function() {
var connectDropTarget = this.props.connectDropTarget; var connectDropTarget = this.props.connectDropTarget;
var TruncatedList = sdk.getComponent('elements.TruncatedList'); var TruncatedList = sdk.getComponent('elements.TruncatedList');
@ -572,13 +353,17 @@ var RoomSubList = React.createClass({
{ subList } { subList }
</div>; </div>;
return this.props.editable ? <Droppable droppableId={"room-sub-list-droppable_" + this.props.tagName}> return this.props.editable ?
{ (provided, snapshot) => ( <Droppable
<div ref={provided.innerRef}> droppableId={"room-sub-list-droppable_" + this.props.tagName}
{ subListContent } type="draggable-RoomTile"
</div> >
) } { (provided, snapshot) => (
</Droppable> : subListContent; <div ref={provided.innerRef}>
{ subListContent }
</div>
) }
</Droppable> : subListContent;
} }
else { else {
var Loader = sdk.getComponent("elements.Spinner"); var Loader = sdk.getComponent("elements.Spinner");

View File

@ -20,6 +20,7 @@ limitations under the License.
import Promise from 'bluebird'; import Promise from 'bluebird';
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PropTypes from 'prop-types';
import sdk from 'matrix-react-sdk'; import sdk from 'matrix-react-sdk';
import { _t, _td } from 'matrix-react-sdk/lib/languageHandler'; import { _t, _td } from 'matrix-react-sdk/lib/languageHandler';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
@ -28,14 +29,15 @@ import DMRoomMap from 'matrix-react-sdk/lib/utils/DMRoomMap';
import * as Rooms from 'matrix-react-sdk/lib/Rooms'; import * as Rooms from 'matrix-react-sdk/lib/Rooms';
import * as RoomNotifs from 'matrix-react-sdk/lib/RoomNotifs'; import * as RoomNotifs from 'matrix-react-sdk/lib/RoomNotifs';
import Modal from 'matrix-react-sdk/lib/Modal'; import Modal from 'matrix-react-sdk/lib/Modal';
import RoomListActions from 'matrix-react-sdk/lib/actions/RoomListActions';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomTileContextMenu', displayName: 'RoomTileContextMenu',
propTypes: { propTypes: {
room: React.PropTypes.object.isRequired, room: PropTypes.object.isRequired,
/* callback called when the menu is dismissed */ /* callback called when the menu is dismissed */
onFinished: React.PropTypes.func, onFinished: PropTypes.func,
}, },
getInitialState() { getInitialState() {
@ -45,7 +47,7 @@ module.exports = React.createClass({
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"), isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"), isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)), isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)),
} };
}, },
componentWillMount: function() { componentWillMount: function() {
@ -57,42 +59,16 @@ module.exports = React.createClass({
}, },
_toggleTag: function(tagNameOn, tagNameOff) { _toggleTag: function(tagNameOn, tagNameOff) {
var self = this; if (!MatrixClientPeg.get().isGuest()) {
const roomId = this.props.room.roomId; Promise.delay(500).then(() => {
var cli = MatrixClientPeg.get(); dis.dispatch(RoomListActions.tagRoom(
if (!cli.isGuest()) { MatrixClientPeg.get(),
Promise.delay(500).then(function() { this.props.room,
if (tagNameOff !== null && tagNameOff !== undefined) { tagNameOff, tagNameOn,
cli.deleteRoomTag(roomId, tagNameOff).finally(function() { undefined, 0,
// Close the context menu ), true);
if (self.props.onFinished) {
self.props.onFinished();
};
}).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to remove tag from room 1', '', ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOff}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
if (tagNameOn !== null && tagNameOn !== undefined) { this.props.onFinished();
// If the tag ordering meta data is required, it is added by
// the RoomSubList when it sorts its rooms
cli.setRoomTag(roomId, tagNameOn, {}).finally(function() {
// Close the context menu
if (self.props.onFinished) {
self.props.onFinished();
};
}).catch(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to remove tag from room 2', '', ErrorDialog, {
title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOn}),
description: ((err && err.message) ? err.message : _t('Operation failed')),
});
});
}
}); });
} }
}, },
@ -132,22 +108,22 @@ module.exports = React.createClass({
}, },
_onClickDM: function() { _onClickDM: function() {
if (MatrixClientPeg.get().isGuest()) return;
const newIsDirectMessage = !this.state.isDirectMessage; const newIsDirectMessage = !this.state.isDirectMessage;
this.setState({ this.setState({
isDirectMessage: newIsDirectMessage, isDirectMessage: newIsDirectMessage,
}); });
if (MatrixClientPeg.get().isGuest()) return;
Rooms.guessAndSetDMRoom( Rooms.guessAndSetDMRoom(
this.props.room, newIsDirectMessage this.props.room, newIsDirectMessage,
).delay(500).finally(() => { ).delay(500).finally(() => {
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {
this.props.onFinished(); this.props.onFinished();
}; }
}, (err) => { }, (err) => {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to set Direct Message status of room', '', ErrorDialog, { Modal.createTrackedDialog('Failed to set Direct Message status of room', '', ErrorDialog, {
title: _t('Failed to set Direct Message status of room'), title: _t('Failed to set Direct Message status of room'),
description: ((err && err.message) ? err.message : _t('Operation failed')), description: ((err && err.message) ? err.message : _t('Operation failed')),
@ -165,7 +141,7 @@ module.exports = React.createClass({
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {
this.props.onFinished(); this.props.onFinished();
}; }
}, },
_onClickReject: function() { _onClickReject: function() {
@ -177,7 +153,7 @@ module.exports = React.createClass({
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {
this.props.onFinished(); this.props.onFinished();
}; }
}, },
_onClickForget: function() { _onClickForget: function() {
@ -185,8 +161,8 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() { MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' }); dis.dispatch({ action: 'view_next_room' });
}, function(err) { }, function(err) {
var errCode = err.errcode || _td("unknown error code"); const errCode = err.errcode || _td("unknown error code");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
title: _t('Failed to forget room %(errCode)s', {errCode: errCode}), title: _t('Failed to forget room %(errCode)s', {errCode: errCode}),
description: ((err && err.message) ? err.message : _t('Operation failed')), description: ((err && err.message) ? err.message : _t('Operation failed')),
@ -196,20 +172,19 @@ module.exports = React.createClass({
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {
this.props.onFinished(); this.props.onFinished();
}; }
}, },
_saveNotifState: function(newState) { _saveNotifState: function(newState) {
if (MatrixClientPeg.get().isGuest()) return;
const oldState = this.state.roomNotifState; const oldState = this.state.roomNotifState;
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get();
if (cli.isGuest()) return;
this.setState({ this.setState({
roomNotifState: newState, roomNotifState: newState,
}); });
RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => { RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
// delay slightly so that the user can see their state change // delay slightly so that the user can see their state change
// before closing the menu // before closing the menu
return Promise.delay(500).then(() => { return Promise.delay(500).then(() => {
@ -217,7 +192,7 @@ module.exports = React.createClass({
// Close the context menu // Close the context menu
if (this.props.onFinished) { if (this.props.onFinished) {
this.props.onFinished(); this.props.onFinished();
}; }
}); });
}, (error) => { }, (error) => {
// TODO: some form of error notification to the user // TODO: some form of error notification to the user
@ -247,22 +222,22 @@ module.exports = React.createClass({
}, },
_renderNotifMenu: function() { _renderNotifMenu: function() {
var alertMeClasses = classNames({ const alertMeClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD,
}); });
var allNotifsClasses = classNames({ const allNotifsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES,
}); });
var mentionsClasses = classNames({ const mentionsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY,
}); });
var muteNotifsClasses = classNames({ const muteNotifsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE,
}); });
@ -272,22 +247,22 @@ module.exports = React.createClass({
<div className="mx_RoomTileContextMenu_notif_picker" > <div className="mx_RoomTileContextMenu_notif_picker" >
<img src="img/notif-slider.svg" width="20" height="107" /> <img src="img/notif-slider.svg" width="20" height="107" />
</div> </div>
<div className={ alertMeClasses } onClick={this._onClickAlertMe} > <div className={alertMeClasses} onClick={this._onClickAlertMe} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" /> <img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" /> <img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
{ _t('All messages (noisy)') } { _t('All messages (noisy)') }
</div> </div>
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} > <div className={allNotifsClasses} onClick={this._onClickAllNotifs} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" /> <img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off.svg" width="16" height="12" /> <img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off.svg" width="16" height="12" />
{ _t('All messages') } { _t('All messages') }
</div> </div>
<div className={ mentionsClasses } onClick={this._onClickMentions} > <div className={mentionsClasses} onClick={this._onClickMentions} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" /> <img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-mentions.svg" width="16" height="12" /> <img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-mentions.svg" width="16" height="12" />
{ _t('Mentions only') } { _t('Mentions only') }
</div> </div>
<div className={ muteNotifsClasses } onClick={this._onClickMute} > <div className={muteNotifsClasses} onClick={this._onClickMute} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" /> <img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute.svg" width="16" height="12" /> <img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute.svg" width="16" height="12" />
{ _t('Mute') } { _t('Mute') }
@ -322,7 +297,7 @@ module.exports = React.createClass({
return ( return (
<div> <div>
<div className="mx_RoomTileContextMenu_leave" onClick={ leaveClickHandler } > <div className="mx_RoomTileContextMenu_leave" onClick={leaveClickHandler} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_delete.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_delete.svg" width="15" height="15" />
{ leaveText } { leaveText }
</div> </div>
@ -351,17 +326,17 @@ module.exports = React.createClass({
return ( return (
<div> <div>
<div className={ favouriteClasses } onClick={this._onClickFavourite} > <div className={favouriteClasses} onClick={this._onClickFavourite} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_fave.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_fave.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
{ _t('Favourite') } { _t('Favourite') }
</div> </div>
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} > <div className={lowPriorityClasses} onClick={this._onClickLowPriority} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_low.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_low.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
{ _t('Low Priority') } { _t('Low Priority') }
</div> </div>
<div className={ dmClasses } onClick={this._onClickDM} > <div className={dmClasses} onClick={this._onClickDM} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_person.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_person.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" /> <img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" />
{ _t('Direct Chat') } { _t('Direct Chat') }
@ -372,7 +347,7 @@ module.exports = React.createClass({
render: function() { render: function() {
const myMember = this.props.room.getMember( const myMember = this.props.room.getMember(
MatrixClientPeg.get().credentials.userId MatrixClientPeg.get().credentials.userId,
); );
// Can't set notif level or tags on non-join rooms // Can't set notif level or tags on non-join rooms
@ -389,5 +364,5 @@ module.exports = React.createClass({
{ this._renderRoomTagMenu() } { this._renderRoomTagMenu() }
</div> </div>
); );
} },
}); });

View File

@ -41,6 +41,7 @@ export default class DNDRoomTile extends React.Component {
key={props.room.roomId} key={props.room.roomId}
draggableId={props.tagName + '_' + props.room.roomId} draggableId={props.tagName + '_' + props.room.roomId}
index={props.index} index={props.index}
type="draggable-RoomTile"
> >
{ (provided, snapshot) => { { (provided, snapshot) => {
return ( return (

View File

@ -21,6 +21,10 @@ limitations under the License.
flex-direction: column; flex-direction: column;
} }
.mx_LeftPanel_container {
display: flex;
}
.mx_LeftPanel_hideButton { .mx_LeftPanel_hideButton {
position: absolute; position: absolute;
top: 10px; top: 10px;