From 2926154dce90671293a28348f00710c3593aee8e Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Thu, 25 Jan 2018 22:30:58 +0100 Subject: [PATCH 01/13] Modify LeftPanel to include a DragDropContext that handles drag and drop for TagPanel and RoomList. This is to allow the future feature of dragging between the two components. --- src/components/structures/LeftPanel.js | 86 ++++++++++++++++--- .../css/vector-web/structures/_LeftPanel.scss | 4 + 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index f78835b4..c1222396 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -17,22 +17,32 @@ limitations under the License. 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; 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 sdk from 'matrix-react-sdk'; 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 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({ displayName: 'LeftPanel', // NB. If you add props, don't forget to update // shouldComponentUpdate! propTypes: { - collapsed: React.PropTypes.bool.isRequired, + collapsed: PropTypes.bool.isRequired, + }, + + contextTypes: { + matrixClient: PropTypes.instanceOf(MatrixClient), }, getInitialState: function() { @@ -161,8 +171,54 @@ var LeftPanel = React.createClass({ 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, + MatrixClientPeg.get().getRoom(roomId), + prevTag, newTag, + oldIndex, newIndex, + ), true); + }, + + collectRoomList: function(ref) { + this._roomList = ref; + }, + render: function() { const RoomList = sdk.getComponent('rooms.RoomList'); + const TagPanel = sdk.getComponent('structures.TagPanel'); const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu'); const CallPreview = sdk.getComponent('voip.CallPreview'); @@ -184,15 +240,21 @@ var LeftPanel = React.createClass({ ); return ( - + +
+ { SettingsStore.isFeatureEnabled("feature_tag_panel") ? :
} + +
+ ); } }); diff --git a/src/skins/vector/css/vector-web/structures/_LeftPanel.scss b/src/skins/vector/css/vector-web/structures/_LeftPanel.scss index 8ae1fe15..a2147a02 100644 --- a/src/skins/vector/css/vector-web/structures/_LeftPanel.scss +++ b/src/skins/vector/css/vector-web/structures/_LeftPanel.scss @@ -21,6 +21,10 @@ limitations under the License. flex-direction: column; } +.mx_LeftPanel_container { + display: flex; +} + .mx_LeftPanel_hideButton { position: absolute; top: 10px; From 37fe3ac676ee06bd832cfa6762394bcc1bcbcc4c Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Thu, 25 Jan 2018 22:51:40 +0100 Subject: [PATCH 02/13] Prevent RoomTiles from being dragged into other droppables for the time being at least. --- src/components/structures/RoomSubList.js | 18 +++++++++++------- src/components/views/rooms/DNDRoomTile.js | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 15f56c00..398f2647 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -572,13 +572,17 @@ var RoomSubList = React.createClass({ { subList }
; - return this.props.editable ? - { (provided, snapshot) => ( -
- { subListContent } -
- ) } -
: subListContent; + return this.props.editable ? + + { (provided, snapshot) => ( +
+ { subListContent } +
+ ) } +
: subListContent; } else { var Loader = sdk.getComponent("elements.Spinner"); diff --git a/src/components/views/rooms/DNDRoomTile.js b/src/components/views/rooms/DNDRoomTile.js index 129e3f45..b8f8b402 100644 --- a/src/components/views/rooms/DNDRoomTile.js +++ b/src/components/views/rooms/DNDRoomTile.js @@ -41,6 +41,7 @@ export default class DNDRoomTile extends React.Component { key={props.room.roomId} draggableId={props.tagName + '_' + props.room.roomId} index={props.index} + type="draggable-RoomTile" > { (provided, snapshot) => { return ( From e46f436a472a017889f146bc4fcad1d1b2d379ac Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Tue, 6 Feb 2018 11:50:34 +0000 Subject: [PATCH 03/13] Prefer context.matrixClient in LeftPanel --- src/components/structures/LeftPanel.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index c1222396..398862f2 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -24,7 +24,6 @@ import { MatrixClient } from 'matrix-js-sdk'; import { KeyCode } from 'matrix-react-sdk/lib/Keyboard'; import sdk from 'matrix-react-sdk'; import dis from 'matrix-react-sdk/lib/dispatcher'; -import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; import VectorConferenceHandler from '../../VectorConferenceHandler'; import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore"; @@ -206,7 +205,7 @@ var LeftPanel = React.createClass({ dis.dispatch(RoomListActions.tagRoom( this.context.matrixClient, - MatrixClientPeg.get().getRoom(roomId), + this.context.matrixClient.getRoom(roomId), prevTag, newTag, oldIndex, newIndex, ), true); @@ -223,7 +222,7 @@ var LeftPanel = React.createClass({ const CallPreview = sdk.getComponent('voip.CallPreview'); let topBox; - if (MatrixClientPeg.get().isGuest()) { + if (this.context.matrixClient.isGuest()) { const LoginBox = sdk.getComponent('structures.LoginBox'); topBox = ; } else { From 83996c09d9a06f34fa11a07ab6c789c31770082f Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Tue, 6 Feb 2018 11:51:46 +0000 Subject: [PATCH 04/13] General delint, cleanup --- .../context_menus/RoomTileContextMenu.js | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index 36602cfa..9ddafb83 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -20,6 +20,7 @@ limitations under the License. import Promise from 'bluebird'; import React from 'react'; import classNames from 'classnames'; +import PropTypes from 'prop-types'; import sdk from 'matrix-react-sdk'; import { _t, _td } from 'matrix-react-sdk/lib/languageHandler'; 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 RoomNotifs from 'matrix-react-sdk/lib/RoomNotifs'; import Modal from 'matrix-react-sdk/lib/Modal'; +import RoomListActions from 'matrix-react-sdk/lib/actions/RoomListActions'; module.exports = React.createClass({ displayName: 'RoomTileContextMenu', propTypes: { - room: React.PropTypes.object.isRequired, + room: PropTypes.object.isRequired, /* callback called when the menu is dismissed */ - onFinished: React.PropTypes.func, + onFinished: PropTypes.func, }, getInitialState() { @@ -45,7 +47,7 @@ module.exports = React.createClass({ isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"), isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"), isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)), - } + }; }, componentWillMount: function() { @@ -132,22 +134,22 @@ module.exports = React.createClass({ }, _onClickDM: function() { + if (MatrixClientPeg.get().isGuest()) return; + const newIsDirectMessage = !this.state.isDirectMessage; this.setState({ isDirectMessage: newIsDirectMessage, }); - if (MatrixClientPeg.get().isGuest()) return; - Rooms.guessAndSetDMRoom( - this.props.room, newIsDirectMessage + this.props.room, newIsDirectMessage, ).delay(500).finally(() => { // Close the context menu if (this.props.onFinished) { this.props.onFinished(); - }; + } }, (err) => { - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to set Direct Message status of room', '', ErrorDialog, { title: _t('Failed to set Direct Message status of room'), description: ((err && err.message) ? err.message : _t('Operation failed')), @@ -165,7 +167,7 @@ module.exports = React.createClass({ // Close the context menu if (this.props.onFinished) { this.props.onFinished(); - }; + } }, _onClickReject: function() { @@ -177,7 +179,7 @@ module.exports = React.createClass({ // Close the context menu if (this.props.onFinished) { this.props.onFinished(); - }; + } }, _onClickForget: function() { @@ -185,8 +187,8 @@ module.exports = React.createClass({ MatrixClientPeg.get().forget(this.props.room.roomId).done(function() { dis.dispatch({ action: 'view_next_room' }); }, function(err) { - var errCode = err.errcode || _td("unknown error code"); - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const errCode = err.errcode || _td("unknown error code"); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, { title: _t('Failed to forget room %(errCode)s', {errCode: errCode}), description: ((err && err.message) ? err.message : _t('Operation failed')), @@ -196,20 +198,19 @@ module.exports = React.createClass({ // Close the context menu if (this.props.onFinished) { this.props.onFinished(); - }; + } }, _saveNotifState: function(newState) { + if (MatrixClientPeg.get().isGuest()) return; + const oldState = this.state.roomNotifState; const roomId = this.props.room.roomId; - var cli = MatrixClientPeg.get(); - - if (cli.isGuest()) return; this.setState({ 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 // before closing the menu return Promise.delay(500).then(() => { @@ -217,7 +218,7 @@ module.exports = React.createClass({ // Close the context menu if (this.props.onFinished) { this.props.onFinished(); - }; + } }); }, (error) => { // TODO: some form of error notification to the user @@ -247,22 +248,22 @@ module.exports = React.createClass({ }, _renderNotifMenu: function() { - var alertMeClasses = classNames({ + const alertMeClasses = classNames({ 'mx_RoomTileContextMenu_notif_field': true, '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_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES, }); - var mentionsClasses = classNames({ + const mentionsClasses = classNames({ 'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY, }); - var muteNotifsClasses = classNames({ + const muteNotifsClasses = classNames({ 'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE, }); @@ -272,22 +273,22 @@ module.exports = React.createClass({
-
+
{ _t('All messages (noisy)') }
-
+
{ _t('All messages') }
-
+
{ _t('Mentions only') }
-
+
{ _t('Mute') } @@ -322,7 +323,7 @@ module.exports = React.createClass({ return (
-
+
{ leaveText }
@@ -351,17 +352,17 @@ module.exports = React.createClass({ return (
-
+
{ _t('Favourite') }
-
+
{ _t('Low Priority') }
-
+
{ _t('Direct Chat') } @@ -372,7 +373,7 @@ module.exports = React.createClass({ render: function() { 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 @@ -389,5 +390,5 @@ module.exports = React.createClass({ { this._renderRoomTagMenu() }
); - } + }, }); From 662942ec295183b185e50883b843c63bd4b3aa42 Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Tue, 6 Feb 2018 11:52:39 +0000 Subject: [PATCH 05/13] Use dispatch to change room tags from RoomTileContextMenu --- .../context_menus/RoomTileContextMenu.js | 44 ++++--------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index 9ddafb83..06eb347d 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -59,42 +59,16 @@ module.exports = React.createClass({ }, _toggleTag: function(tagNameOn, tagNameOff) { - var self = this; - const roomId = this.props.room.roomId; - var cli = MatrixClientPeg.get(); - if (!cli.isGuest()) { - Promise.delay(500).then(function() { - if (tagNameOff !== null && tagNameOff !== undefined) { - cli.deleteRoomTag(roomId, tagNameOff).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 1', '', ErrorDialog, { - title: _t('Failed to remove tag %(tagName)s from room', {tagName: tagNameOff}), - description: ((err && err.message) ? err.message : _t('Operation failed')), - }); - }); - } + if (!MatrixClientPeg.get().isGuest()) { + Promise.delay(500).then(() => { + dis.dispatch(RoomListActions.tagRoom( + MatrixClientPeg.get(), + this.props.room, + tagNameOff, tagNameOn, + undefined, 0, + ), true); - if (tagNameOn !== null && tagNameOn !== undefined) { - // 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')), - }); - }); - } + this.props.onFinished(); }); } }, From 495090c9ba41a05cc74acb27dc6bb31782a6ed1a Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 8 Feb 2018 14:08:20 +0000 Subject: [PATCH 06/13] Remove now unused RoomSubList code --- src/components/structures/RoomSubList.js | 117 ----------------------- 1 file changed, 117 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 398f2647..8bea76ab 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -38,31 +38,6 @@ var debug = false; 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({ displayName: 'RoomSubList', @@ -279,98 +254,6 @@ var RoomSubList = React.createClass({ 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() { var self = this; var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile"); From 0cdebcce89605e3297cc72fac09038a8d9aa3c38 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 8 Feb 2018 16:28:42 +0000 Subject: [PATCH 07/13] Move sublist sorting to RoomListStore NB: fixUndefinedOrdering was removed completely because it was code dealing with legacy favourites. see matrix-org/matrix-react-sdk@9b0df191 --- src/components/structures/RoomSubList.js | 114 ++--------------------- 1 file changed, 6 insertions(+), 108 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 8bea76ab..d119e271 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -85,13 +85,17 @@ var RoomSubList = React.createClass({ }, 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) { // order the room list appropriately before we re-render //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) { @@ -139,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) { const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD]; return showBadgeInStates.indexOf(roomNotifState) > -1; @@ -380,47 +319,6 @@ var RoomSubList = React.createClass({ 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() { var connectDropTarget = this.props.connectDropTarget; var TruncatedList = sdk.getComponent('elements.TruncatedList'); From d47c9d8b6ee6740ef1de3a7f63605ceff68bc77e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 12 Feb 2018 18:02:55 +0000 Subject: [PATCH 08/13] Add context menu to TagPanel See https://github.com/matrix-org/matrix-react-sdk/pull/1743 --- .../views/context_menus/TagTileContextMenu.js | 68 +++++++++++++++++++ src/i18n/strings/en_EN.json | 17 ++--- src/skins/vector/css/_components.scss | 5 +- .../structures/_TagPanel.scss | 18 +++++ .../context_menus/_TagTileContextMenu.scss | 44 ++++++++++++ 5 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 src/components/views/context_menus/TagTileContextMenu.js create mode 100644 src/skins/vector/css/vector-web/views/context_menus/_TagTileContextMenu.scss diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js new file mode 100644 index 00000000..4ec6c515 --- /dev/null +++ b/src/components/views/context_menus/TagTileContextMenu.js @@ -0,0 +1,68 @@ +/* +Copyright 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. +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 { _t } from 'matrix-react-sdk/lib/languageHandler'; +import dis from 'matrix-react-sdk/lib/dispatcher'; +import TagOrderActions from 'matrix-react-sdk/lib/actions/TagOrderActions'; +import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; + +export default class TagTileContextMenu extends React.Component { + static propTypes = { + tag: PropTypes.string.isRequired, + /* callback called when the menu is dismissed */ + onFinished: PropTypes.func.isRequired, + }; + + constructor() { + super(); + + this._onViewCommunityClick = this._onViewCommunityClick.bind(this); + this._onRemoveClick = this._onRemoveClick.bind(this); + } + + _onViewCommunityClick() { + dis.dispatch({ + action: 'view_group', + group_id: this.props.tag, + }); + this.props.onFinished(); + } + + _onRemoveClick() { + dis.dispatch(TagOrderActions.removeTag( + // XXX: Context menus don't have a MatrixClient context + MatrixClientPeg.get(), + this.props.tag, + )); + this.props.onFinished(); + } + + render() { + return
+
+ + { _t('View Community') } +
+
+
+ + { _t('Remove') } +
+
; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 819c0cfa..bbb94a60 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -43,9 +43,6 @@ "Off": "Off", "On": "On", "Noisy": "Noisy", - "Failed to set direct chat tag": "Failed to set direct chat tag", - "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", "Search…": "Search…", "Search": "Search", "This Room": "This Room", @@ -68,8 +65,6 @@ "What's New": "What's New", "Update": "Update", "What's new?": "What's new?", - "Appear Offline": "Appear Offline", - "Away": "Away", "A new version of Riot is available.": "A new version of Riot is available.", "To return to your account in future you need to set a password": "To return to your account in future you need to set a password", "Set Password": "Set Password", @@ -126,6 +121,7 @@ "Resend": "Resend", "Cancel Sending": "Cancel Sending", "Forward Message": "Forward Message", + "Reply": "Reply", "Unpin Message": "Unpin Message", "Pin Message": "Pin Message", "View Source": "View Source", @@ -134,6 +130,10 @@ "Permalink": "Permalink", "Quote": "Quote", "Source URL": "Source URL", + "Online": "Online", + "Away": "Away", + "Appear Offline": "Appear Offline", + "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", "Failed to set Direct Message status of room": "Failed to set Direct Message status of room", "unknown error code": "unknown error code", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", @@ -147,6 +147,7 @@ "Favourite": "Favourite", "Low Priority": "Low Priority", "Direct Chat": "Direct Chat", + "View Community": "View Community", "Sorry, your browser is not able to run Riot.": "Sorry, your browser is not able to run Riot.", "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.", "Please install Chrome or Firefox for the best experience.": "Please install Chrome or Firefox for the best experience.", @@ -156,10 +157,10 @@ "Couldn't load home page": "Couldn't load home page", "Login": "Login", "Register": "Register", - "Invite to this room": "Invite to this room", "Members": "Members", "%(count)s Members|other": "%(count)s Members", "%(count)s Members|one": "%(count)s Member", + "Invite to this room": "Invite to this room", "Files": "Files", "Notifications": "Notifications", "Rooms": "Rooms", @@ -189,6 +190,7 @@ "Search for a room": "Search for a room", "#example": "#example", "more": "more", + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", "Expand panel": "Expand panel", "Collapse panel": "Collapse panel", "Filter room names": "Filter room names", @@ -219,6 +221,5 @@ "Contributing code to Matrix and Riot": "Contributing code to Matrix and Riot", "Dev chat for the Riot/Web dev team": "Dev chat for the Riot/Web dev team", "Dev chat for the Dendrite dev team": "Dev chat for the Dendrite dev team", - "Co-ordination for Riot/Web translators": "Co-ordination for Riot/Web translators", - "Reply": "Reply" + "Co-ordination for Riot/Web translators": "Co-ordination for Riot/Web translators" } diff --git a/src/skins/vector/css/_components.scss b/src/skins/vector/css/_components.scss index faf358bd..837fe453 100644 --- a/src/skins/vector/css/_components.scss +++ b/src/skins/vector/css/_components.scss @@ -36,10 +36,10 @@ @import "./matrix-react-sdk/views/elements/_EditableItemList.scss"; @import "./matrix-react-sdk/views/elements/_MemberEventListSummary.scss"; @import "./matrix-react-sdk/views/elements/_ProgressBar.scss"; +@import "./matrix-react-sdk/views/elements/_Quote.scss"; @import "./matrix-react-sdk/views/elements/_RichText.scss"; @import "./matrix-react-sdk/views/elements/_RoleButton.scss"; @import "./matrix-react-sdk/views/elements/_ToolTipButton.scss"; -@import "./matrix-react-sdk/views/elements/_Quote.scss"; @import "./matrix-react-sdk/views/groups/_GroupPublicityToggle.scss"; @import "./matrix-react-sdk/views/groups/_GroupRoomList.scss"; @import "./matrix-react-sdk/views/groups/_GroupUserSettings.scss"; @@ -65,6 +65,7 @@ @import "./matrix-react-sdk/views/rooms/_PinnedEventTile.scss"; @import "./matrix-react-sdk/views/rooms/_PinnedEventsPanel.scss"; @import "./matrix-react-sdk/views/rooms/_PresenceLabel.scss"; +@import "./matrix-react-sdk/views/rooms/_QuotePreview.scss"; @import "./matrix-react-sdk/views/rooms/_RoomHeader.scss"; @import "./matrix-react-sdk/views/rooms/_RoomList.scss"; @import "./matrix-react-sdk/views/rooms/_RoomPreviewBar.scss"; @@ -72,7 +73,6 @@ @import "./matrix-react-sdk/views/rooms/_RoomTile.scss"; @import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss"; @import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss"; -@import "./matrix-react-sdk/views/rooms/_QuotePreview.scss"; @import "./matrix-react-sdk/views/settings/_DevicesPanel.scss"; @import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss"; @import "./matrix-react-sdk/views/voip/_CallView.scss"; @@ -89,6 +89,7 @@ @import "./vector-web/views/context_menus/_MessageContextMenu.scss"; @import "./vector-web/views/context_menus/_PresenceContextMenuOption.scss"; @import "./vector-web/views/context_menus/_RoomTileContextMenu.scss"; +@import "./vector-web/views/context_menus/_TagTileContextMenu.scss"; @import "./vector-web/views/dialogs/_ChangelogDialog.scss"; @import "./vector-web/views/dialogs/_DevtoolsDialog.scss"; @import "./vector-web/views/dialogs/_SetEmailDialog.scss"; diff --git a/src/skins/vector/css/matrix-react-sdk/structures/_TagPanel.scss b/src/skins/vector/css/matrix-react-sdk/structures/_TagPanel.scss index 91199a11..ba745b11 100644 --- a/src/skins/vector/css/matrix-react-sdk/structures/_TagPanel.scss +++ b/src/skins/vector/css/matrix-react-sdk/structures/_TagPanel.scss @@ -37,6 +37,7 @@ limitations under the License. .mx_TagPanel .mx_TagTile { padding: 6px 3px; opacity: 0.5; + position: relative; } .mx_TagPanel .mx_TagTile:focus, .mx_TagPanel .mx_TagTile:hover, @@ -64,6 +65,23 @@ limitations under the License. left: 5px; } +.mx_TagTile_context_button { + min-width: 15px; + height: 15px; + position: absolute; + right: -5px; + top: 1px; + border-radius: 8px; + background-color: $neutral-badge-color; + color: #ffffff; + font-weight: 600; + font-size: 10px; + text-align: center; + padding-top: 1px; + padding-left: 4px; + padding-right: 4px; +} + .mx_TagPanel_createGroupButton { opacity: 0.5; margin-bottom: 17px; diff --git a/src/skins/vector/css/vector-web/views/context_menus/_TagTileContextMenu.scss b/src/skins/vector/css/vector-web/views/context_menus/_TagTileContextMenu.scss new file mode 100644 index 00000000..759b92bd --- /dev/null +++ b/src/skins/vector/css/vector-web/views/context_menus/_TagTileContextMenu.scss @@ -0,0 +1,44 @@ +/* +Copyright 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. +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. +*/ + +.mx_TagTileContextMenu_item { + padding-top: 8px; + padding-right: 20px; + padding-bottom: 8px; + cursor: pointer; + white-space: nowrap; + display: flex; + align-items: center; + line-height: 16px; +} + + +.mx_TagTileContextMenu_item_icon { + padding-right: 8px; + padding-left: 4px; + display: inline-block +} + +.mx_TagTileContextMenu_separator { + margin-top: 0; + margin-bottom: 0; + border-bottom-style: none; + border-left-style: none; + border-right-style: none; + border-top-style: solid; + border-top-width: 1px; + border-color: $menu-border-color; +} From e36baa8c3a66cd5d256b5d46ae11b43bd7edd2e5 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 12 Feb 2018 18:47:29 +0000 Subject: [PATCH 09/13] Fix quotes on import --- src/components/structures/LeftPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 398862f2..3260c975 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -26,7 +26,7 @@ import sdk from 'matrix-react-sdk'; import dis from 'matrix-react-sdk/lib/dispatcher'; import VectorConferenceHandler from '../../VectorConferenceHandler'; -import SettingsStore from "matrix-react-sdk/lib/settings/SettingsStore"; +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'; From 285c4f91d38f72c7283549b7d2821cfcb50632a9 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 13 Feb 2018 15:17:51 +0000 Subject: [PATCH 10/13] Fix TagPanel from collapsing to < 60px when LP collapsed Fixes https://github.com/vector-im/riot-web/issues/6133 --- src/components/structures/LeftPanel.js | 7 ++++++- .../vector/css/matrix-react-sdk/structures/_TagPanel.scss | 2 +- src/skins/vector/css/vector-web/structures/_LeftPanel.scss | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 3260c975..0d9ab641 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -238,9 +238,14 @@ var LeftPanel = React.createClass({ } ); + const containerClasses = classNames( + "mx_LeftPanel_container", + { "mx_LeftPanel_container_collapsed": this.props.collapsed }, + ); + return ( -
+
{ SettingsStore.isFeatureEnabled("feature_tag_panel") ? :
}