From 3481283af8f9921fcd7b5c8eb4941822b123434b Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Thu, 18 Jan 2018 17:54:12 +0000 Subject: [PATCH 1/4] Swap RoomList to react-beautiful-dnd Includes themeing See matrix-org/matrix-react-sdk#1711 --- package.json | 1 + src/components/structures/RoomSubList.js | 38 +-- src/components/views/rooms/DNDRoomTile.js | 253 +++--------------- .../views/rooms/_RoomTile.scss | 2 + src/skins/vector/css/themes/_base.scss | 1 + src/skins/vector/css/themes/_dark.scss | 3 +- .../vector-web/structures/_RoomSubList.scss | 4 + .../vector/themes/status/css/_status.scss | 1 + 8 files changed, 68 insertions(+), 235 deletions(-) diff --git a/package.json b/package.json index 556f0c724c..65f866dcaf 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "pako": "^1.0.5", "prop-types": "^15.5.10", "react": "^15.6.0", + "react-beautiful-dnd": "^4.0.1", "react-dnd": "^2.1.4", "react-dnd-html5-backend": "^2.1.2", "react-dom": "^15.6.0", diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 251c65226c..15f56c0069 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -20,8 +20,8 @@ limitations under the License. var React = require('react'); var ReactDOM = require('react-dom'); var classNames = require('classnames'); -var DropTarget = require('react-dnd').DropTarget; var sdk = require('matrix-react-sdk'); +import { Droppable } from 'react-beautiful-dnd'; import { _t } from 'matrix-react-sdk/lib/languageHandler'; var dis = require('matrix-react-sdk/lib/dispatcher'); var Unread = require('matrix-react-sdk/lib/Unread'); @@ -32,6 +32,7 @@ var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/A import Modal from 'matrix-react-sdk/lib/Modal'; import { KeyCode } from 'matrix-react-sdk/lib/Keyboard'; + // turn this on for drop & drag console debugging galore var debug = false; @@ -326,9 +327,7 @@ var RoomSubList = React.createClass({ }); }, - calcManualOrderTagData: function(room) { - const index = this.state.sortedList.indexOf(room); - + 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. @@ -375,12 +374,14 @@ var RoomSubList = React.createClass({ makeRoomTiles: function() { var self = this; var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile"); - return this.state.sortedList.map(function(room) { + return this.state.sortedList.map(function(room, index) { // XXX: is it evil to pass in self as a prop to RoomTile? return ( ; } - return connectDropTarget( -
- { this._getHeaderJsx() } - { subList } -
- ); + const subListContent =
+ { this._getHeaderJsx() } + { subList } +
; + + return this.props.editable ? + { (provided, snapshot) => ( +
+ { subListContent } +
+ ) } +
: subListContent; } else { var Loader = sdk.getComponent("elements.Spinner"); @@ -585,11 +592,4 @@ var RoomSubList = React.createClass({ } }); -// Export the wrapped version, inlining the 'collect' functions -// to more closely resemble the ES7 -module.exports = -DropTarget('RoomTile', roomListTarget, function(connect) { - return { - connectDropTarget: connect.dropTarget(), - } -})(RoomSubList); +module.exports = RoomSubList; diff --git a/src/components/views/rooms/DNDRoomTile.js b/src/components/views/rooms/DNDRoomTile.js index 430906d21b..1adb2a79a3 100644 --- a/src/components/views/rooms/DNDRoomTile.js +++ b/src/components/views/rooms/DNDRoomTile.js @@ -14,227 +14,50 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; - import React from 'react'; -import {DragSource} from 'react-dnd'; -import {DropTarget} from 'react-dnd'; - -import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg'; -import sdk from 'matrix-react-sdk'; -import { _t } from 'matrix-react-sdk/lib/languageHandler'; +import { Draggable } from 'react-beautiful-dnd'; import RoomTile from 'matrix-react-sdk/lib/components/views/rooms/RoomTile'; -import * as Rooms from 'matrix-react-sdk/lib/Rooms'; -import Modal from 'matrix-react-sdk/lib/Modal'; -/** - * Defines a new Component, DNDRoomTile that wraps RoomTile, making it draggable. - * Requires extra props: - * roomSubList: React.PropTypes.object.isRequired, - * refreshSubList: React.PropTypes.func.isRequired, - */ +export default class DNDRoomTile extends React.Component { + constructor() { + super(); + this.getStyle = this.getStyle.bind(this); + } -/** - * Specifies the drag source contract. - * Only `beginDrag` function is required. - */ -var roomTileSource = { - canDrag: function(props, monitor) { - return props.roomSubList.props.editable; - }, - - beginDrag: function (props) { - // Return the data describing the dragged item - var item = { - room: props.room, - originalList: props.roomSubList, - originalIndex: props.roomSubList.findRoomTile(props.room).index, - targetList: props.roomSubList, // at first target is same as original - // lastTargetRoom: null, - // lastYOffset: null, - // lastYDelta: null, + getStyle(isDragging) { + const result = { + transform: isDragging ? "scale(1.05, 1.05)" : "none", + transition: "transform 0.2s", }; - - if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId); - - // doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions - props.room._dragging = true; - - return item; - }, - - endDrag: function (props, monitor, component) { - var item = monitor.getItem(); - - if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop()); - - props.room._dragging = false; - if (monitor.didDrop()) { - if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label); - item.targetList.forceUpdate(); // as we're not using state - } - - const prevTag = item.originalList.props.tagName; - const newTag = item.targetList.props.tagName; - - if (monitor.didDrop() && item.targetList.props.editable) { - // Evil hack to get DMs behaving - if ((prevTag === undefined && newTag === 'im.vector.fake.direct') || - (prevTag === 'im.vector.fake.direct' && newTag === undefined) - ) { - Rooms.guessAndSetDMRoom( - item.room, newTag === 'im.vector.fake.direct', - ).done(() => { - item.originalList.removeRoomTile(item.room); - }, (err) => { - const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to set direct chat tag " + err); - Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, { - title: _t('Failed to set direct chat tag'), - description: ((err && err.message) ? err.message : _t('Operation failed')), - }); - }); - return; - } - - // More evilness: We will still be dealing with moving to favourites/low prio, - // but we avoid ever doing a request with 'im.vector.fake.direct`. - - // if we moved lists, remove the old tag - if (prevTag && prevTag !== 'im.vector.fake.direct' && - item.targetList !== item.originalList - ) { - // commented out attempts to set a spinner on our target component as component is actually - // the original source component being dragged, not our target. To fix we just need to - // move all of this to endDrop in the target instead. FIXME later. - - //component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 }); - MatrixClientPeg.get().deleteRoomTag(item.room.roomId, prevTag).finally(function() { - //component.state.set({ spinner: component.state.spinner-- }); - }).catch(function(err) { - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to remove tag " + prevTag + " from room: " + err); - Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, { - title: _t('Failed to remove tag %(tagName)s from room', {tagName: prevTag}), - description: ((err && err.message) ? err.message : _t('Operation failed')), - }); - }); - } - - var newOrder= {}; - if (item.targetList.props.order === 'manual') { - newOrder['order'] = item.targetList.calcManualOrderTagData(item.room); - } - - // if we moved lists or the ordering changed, add the new tag - if (newTag && newTag !== 'im.vector.fake.direct' && - (item.targetList !== item.originalList || newOrder) - ) { - MatrixClientPeg.get().setRoomTag(item.room.roomId, newTag, newOrder).catch(function(err) { - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - console.error("Failed to add tag " + newTag + " to room: " + err); - Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, { - title: _t('Failed to add tag %(tagName)s to room', {tagName: newTag}), - description: ((err && err.message) ? err.message : _t('Operation failed')), - }); - }); - } - } - else { - // cancel the drop and reset our original position - if (props.roomSubList.debug) console.log("cancelling drop & drag"); - props.roomSubList.moveRoomTile(item.room, item.originalIndex); - if (item.targetList && item.targetList !== item.originalList) { - item.targetList.removeRoomTile(item.room); - } - } + return result; } -}; -var roomTileTarget = { - canDrop: function() { - return false; - }, + render() { + const props = this.props; - hover: function(props, monitor) { - var item = monitor.getItem(); - //var off = monitor.getClientOffset(); - // console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver()); - - //console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList); - - var switchedTarget = false; - if (item.targetList !== props.roomSubList) { - // we've switched target, so remove the tile from the previous target. - // n.b. the previous target might actually be the source list. - if (props.roomSubList.debug) console.log("switched target sublist"); - switchedTarget = true; - item.targetList.removeRoomTile(item.room); - item.targetList = props.roomSubList; - } - - if (!item.targetList.props.editable) return; - - if (item.targetList.props.order === 'manual') { - if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) { - // find the offset of the target tile in the list. - var roomTile = props.roomSubList.findRoomTile(props.room); - // shuffle the list to add our tile to that position. - props.roomSubList.moveRoomTile(item.room, roomTile.index); - } - - // stop us from flickering between our droptarget and the previous room. - // whenever the cursor changes direction we have to reset the flicker-damping. -/* - var yDelta = off.y - item.lastYOffset; - - if ((yDelta > 0 && item.lastYDelta < 0) || - (yDelta < 0 && item.lastYDelta > 0)) - { - // the cursor changed direction - forget our previous room - item.lastTargetRoom = null; - } - else { - // track the last room we were hovering over so we can stop - // bouncing back and forth if the droptarget is narrower than - // the other list items. The other way to do this would be - // to reduce the size of the hittarget on the list items, but - // can't see an easy way to do that. - item.lastTargetRoom = props.room; - } - - if (yDelta) item.lastYDelta = yDelta; - item.lastYOffset = off.y; -*/ - } - else if (switchedTarget) { - if (!props.roomSubList.findRoomTile(item.room).room) { - // add to the list in the right place - props.roomSubList.moveRoomTile(item.room, 0); - } - // we have to sort the list whatever to recalculate it - props.roomSubList.sortList(); - } - }, -}; - -// Export the wrapped version, inlining the 'collect' functions -// to more closely resemble the ES7 -module.exports = -DropTarget('RoomTile', roomTileTarget, function(connect, monitor) { - return { - // Call this function inside render() - // to let React DnD handle the drag events: - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), + return
+ + { (provided, snapshot) => { + return ( +
+
+
+ +
+
+ { provided.placeholder } +
+ ); + } } +
+
; } -})( -DragSource('RoomTile', roomTileSource, function(connect, monitor) { - return { - // Call this function inside render() - // to let React DnD handle the drag events: - connectDragSource: connect.dragSource(), - // You can ask the monitor about the current drag state: - isDragging: monitor.isDragging() - }; -})(RoomTile)); +} diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss index 842228b9c3..c9e7022e22 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss @@ -20,6 +20,8 @@ limitations under the License. font-size: 13px; display: block; height: 34px; + + background-color: $secondary-accent-color; } .mx_RoomTile_tooltip { diff --git a/src/skins/vector/css/themes/_base.scss b/src/skins/vector/css/themes/_base.scss index d5e862aec6..35fc1a7908 100644 --- a/src/skins/vector/css/themes/_base.scss +++ b/src/skins/vector/css/themes/_base.scss @@ -104,6 +104,7 @@ $roomtile-name-color: rgba(69, 69, 69, 0.8); $roomtile-selected-bg-color: rgba(255, 255, 255, 0.8); $roomtile-focused-bg-color: rgba(255, 255, 255, 0.9); +$roomsublist-background: #badece; $roomsublist-label-fg-color: $h3-color; $roomsublist-label-bg-color: $tertiary-accent-color; $roomsublist-chevron-color: $accent-color; diff --git a/src/skins/vector/css/themes/_dark.scss b/src/skins/vector/css/themes/_dark.scss index 60ffeca8ee..b0ca76b8b6 100644 --- a/src/skins/vector/css/themes/_dark.scss +++ b/src/skins/vector/css/themes/_dark.scss @@ -100,9 +100,10 @@ $rte-code-bg-color: #000; // ******************** $roomtile-name-color: rgba(186, 186, 186, 0.8); -$roomtile-selected-bg-color: rgba(255, 255, 255, 0.05); +$roomtile-selected-bg-color: #333; $roomtile-focused-bg-color: rgba(255, 255, 255, 0.2); +$roomsublist-background: #222; $roomsublist-label-fg-color: $h3-color; $roomsublist-label-bg-color: $tertiary-accent-color; $roomsublist-chevron-color: $accent-color; diff --git a/src/skins/vector/css/vector-web/structures/_RoomSubList.scss b/src/skins/vector/css/vector-web/structures/_RoomSubList.scss index db1fb170e5..a2863460ad 100644 --- a/src/skins/vector/css/vector-web/structures/_RoomSubList.scss +++ b/src/skins/vector/css/vector-web/structures/_RoomSubList.scss @@ -18,6 +18,8 @@ limitations under the License. display: table; table-layout: fixed; width: 100%; + + background-color: $roomsublist-background; } .mx_RoomSubList_labelContainer { @@ -155,6 +157,8 @@ limitations under the License. position: relative; cursor: pointer; font-size: 13px; + + background-color: $secondary-accent-color; } .collapsed .mx_RoomSubList_ellipsis { diff --git a/src/skins/vector/themes/status/css/_status.scss b/src/skins/vector/themes/status/css/_status.scss index ed60b83d63..be4c3c7fd7 100644 --- a/src/skins/vector/themes/status/css/_status.scss +++ b/src/skins/vector/themes/status/css/_status.scss @@ -160,6 +160,7 @@ $roomtile-name-color: #ffffff; $roomtile-selected-bg-color: #465561; $roomtile-focused-bg-color: #6d8597; +$roomsublist-background: #465561; $roomsublist-label-fg-color: #ffffff; $roomsublist-label-bg-color: $secondary-accent-color; $roomsublist-chevron-color: #ffffff; From d55d3658f08563daa8ccf1511dd4f67423f2f8fd Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Thu, 18 Jan 2018 18:08:18 +0000 Subject: [PATCH 2/4] Use classNames instead of inline `style` Theres no benefit to using inline styles --- src/components/views/rooms/DNDRoomTile.js | 17 +++++++++-------- .../matrix-react-sdk/views/rooms/_RoomTile.scss | 9 +++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/DNDRoomTile.js b/src/components/views/rooms/DNDRoomTile.js index 1adb2a79a3..129e3f4597 100644 --- a/src/components/views/rooms/DNDRoomTile.js +++ b/src/components/views/rooms/DNDRoomTile.js @@ -18,18 +18,19 @@ import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import RoomTile from 'matrix-react-sdk/lib/components/views/rooms/RoomTile'; +import classNames from 'classnames'; + export default class DNDRoomTile extends React.Component { constructor() { super(); - this.getStyle = this.getStyle.bind(this); + this.getClassName = this.getClassName.bind(this); } - getStyle(isDragging) { - const result = { - transform: isDragging ? "scale(1.05, 1.05)" : "none", - transition: "transform 0.2s", - }; - return result; + getClassName(isDragging) { + return classNames({ + "mx_DNDRoomTile": true, + "mx_DNDRoomTile_dragging": isDragging, + }); } render() { @@ -49,7 +50,7 @@ export default class DNDRoomTile extends React.Component { {...provided.draggableProps} {...provided.dragHandleProps} > -
+
diff --git a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss index c9e7022e22..a59cd3e80b 100644 --- a/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss +++ b/src/skins/vector/css/matrix-react-sdk/views/rooms/_RoomTile.scss @@ -157,6 +157,15 @@ limitations under the License. background-color: $roomtile-selected-bg-color; } +.mx_DNDRoomTile { + transform: none; + transition: transform 0.2s; +} + +.mx_DNDRoomTile_dragging { + transform: scale(1.05, 1.05); +} + .mx_RoomTile:focus { filter: none ! important; background-color: $roomtile-focused-bg-color; From 4504ff954aeb44225f4eee9efadba246a8e1a1c3 Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Fri, 19 Jan 2018 10:10:13 +0000 Subject: [PATCH 3/4] Add `sudo: required` to travis config --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 94ed745cd8..bc3fce38df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,10 @@ dist: trusty # we don't need sudo, so can run in a container, which makes startup much # quicker. -sudo: false +# +# unfortunately we do temporarily require sudo as a workaround for +# https://github.com/travis-ci/travis-ci/issues/8836 +sudo: required language: node_js node_js: From 883ee4ec754c8cdf13879aed0f0e4e44ec931c88 Mon Sep 17 00:00:00 2001 From: lukebarnard Date: Fri, 19 Jan 2018 13:35:11 +0000 Subject: [PATCH 4/4] Remove react-dnd --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 65f866dcaf..87d2aa4329 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,6 @@ "prop-types": "^15.5.10", "react": "^15.6.0", "react-beautiful-dnd": "^4.0.1", - "react-dnd": "^2.1.4", - "react-dnd-html5-backend": "^2.1.2", "react-dom": "^15.6.0", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef", "sanitize-html": "^1.11.1",