From 4a195dd3f065fdd2e75a1c8b1538f3e36100a5ea Mon Sep 17 00:00:00 2001
From: Matthew Hodgson <matthew@matrix.org>
Date: Sat, 7 Nov 2015 02:57:56 +0000
Subject: [PATCH] sacrifice a small mountainside of goats to make
 placeholder-based work correctly

---
 .../vector/css/molecules/RoomDropTarget.css   |  5 ++
 src/skins/vector/css/molecules/RoomTile.css   |  4 +-
 .../vector/views/molecules/RoomDropTarget.js  | 22 +++++---
 src/skins/vector/views/molecules/RoomTile.js  | 55 +++++++++++++++++--
 .../vector/views/organisms/RoomSubList.js     | 23 +++++---
 5 files changed, 88 insertions(+), 21 deletions(-)

diff --git a/src/skins/vector/css/molecules/RoomDropTarget.css b/src/skins/vector/css/molecules/RoomDropTarget.css
index 549fa609..5e3facc4 100644
--- a/src/skins/vector/css/molecules/RoomDropTarget.css
+++ b/src/skins/vector/css/molecules/RoomDropTarget.css
@@ -26,6 +26,11 @@ limitations under the License.
     border-radius: 4px;
 }
 
+.mx_RoomDropTarget_placeholder {
+    padding-top: 1px;
+    padding-bottom: 1px;
+}
+
 .mx_RoomDropTarget_avatar {
     background-color: #fff;
     border-radius: 24px;
diff --git a/src/skins/vector/css/molecules/RoomTile.css b/src/skins/vector/css/molecules/RoomTile.css
index b6255225..4bc71cb8 100644
--- a/src/skins/vector/css/molecules/RoomTile.css
+++ b/src/skins/vector/css/molecules/RoomTile.css
@@ -16,7 +16,8 @@ limitations under the License.
 
 .mx_RoomTile {
     cursor: pointer;
-    display: table-row;
+    /* This fixes wrapping of long room names, but breaks drag & drop previews */
+    /* display: table-row; */
     font-size: 14px;
 }
 
@@ -38,6 +39,7 @@ limitations under the License.
 
 .mx_RoomTile_name {
     display: table-cell;
+    width: 100%;
     vertical-align: middle;
     overflow: hidden;
     text-overflow: ellipsis;
diff --git a/src/skins/vector/views/molecules/RoomDropTarget.js b/src/skins/vector/views/molecules/RoomDropTarget.js
index c1a2a954..00d0546c 100644
--- a/src/skins/vector/views/molecules/RoomDropTarget.js
+++ b/src/skins/vector/views/molecules/RoomDropTarget.js
@@ -22,13 +22,21 @@ module.exports = React.createClass({
     displayName: 'RoomDropTarget',
 
     render: function() {
-        return (
-            <div className="mx_RoomDropTarget">
-                <div className="mx_RoomDropTarget_avatar"></div>
-                <div className="mx_RoomDropTarget_label">
-                    { this.props.label }
+        if (this.props.placeholder) {
+            return (
+                <div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
                 </div>
-            </div>
-        );
+            );
+        }
+        else {
+            return (
+                <div className="mx_RoomDropTarget">
+                    <div className="mx_RoomDropTarget_avatar"></div>
+                    <div className="mx_RoomDropTarget_label">
+                        { this.props.label }
+                    </div>
+                </div>
+            );
+        }
     }
 });
diff --git a/src/skins/vector/views/molecules/RoomTile.js b/src/skins/vector/views/molecules/RoomTile.js
index a8f646ae..28e76a70 100644
--- a/src/skins/vector/views/molecules/RoomTile.js
+++ b/src/skins/vector/views/molecules/RoomTile.js
@@ -43,10 +43,16 @@ var roomTileSource = {
             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,
         };
 
         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;
     },
 
@@ -56,6 +62,11 @@ var roomTileSource = {
 
         console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
 
+        props.room._dragging = false;
+        if (monitor.didDrop()) {
+            monitor.getDropResult().component.forceUpdate(); // as we're not using state
+        }
+
         if (monitor.didDrop() && item.targetList.props.editable) {
             // if we moved lists, remove the old tag
             if (item.targetList !== item.originalList) {
@@ -112,7 +123,8 @@ var roomTileTarget = {
 
     hover: function(props, monitor) {
         var item = monitor.getItem();
-        //console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
+        var off = monitor.getClientOffset();
+        // console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
 
         //console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
 
@@ -120,7 +132,7 @@ var roomTileTarget = {
         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.
-            console.log("switched target");
+            console.log("switched target sublist");
             switchedTarget = true;
             item.targetList.removeRoomTile(item.room);
             item.targetList = props.roomSubList;
@@ -129,10 +141,35 @@ var roomTileTarget = {
         if (!item.targetList.props.editable) return;
 
         if (item.targetList.props.order === 'manual') {
-            if (item.room.roomId !== props.room.roomId) {
+            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) {
@@ -175,6 +212,15 @@ var RoomTile = React.createClass({
     },
 
     render: function() {
+        // if (this.props.clientOffset) {
+        //     //console.log("room " + this.props.room.roomId + " has dropTarget clientOffset " + this.props.clientOffset.x + "," + this.props.clientOffset.y);
+        // }
+
+        if (this.props.room._dragging) {
+            var RoomDropTarget = sdk.getComponent("molecules.RoomDropTarget");
+            return <RoomDropTarget placeholder={true}/>;
+        }
+
         var myUserId = MatrixClientPeg.get().credentials.userId;
         var me = this.props.room.currentState.members[myUserId];
         var classes = classNames({
@@ -247,11 +293,12 @@ var RoomTile = React.createClass({
 // Export the wrapped version, inlining the 'collect' functions
 // to more closely resemble the ES7
 module.exports = 
-DropTarget('RoomTile', roomTileTarget, function(connect) {
+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(),
     }
 })(
 DragSource('RoomTile', roomTileSource, function(connect, monitor) {
diff --git a/src/skins/vector/views/organisms/RoomSubList.js b/src/skins/vector/views/organisms/RoomSubList.js
index b88bc3c7..8dcccc73 100644
--- a/src/skins/vector/views/organisms/RoomSubList.js
+++ b/src/skins/vector/views/organisms/RoomSubList.js
@@ -26,6 +26,11 @@ var roomListTarget = {
         return true;
     },
 
+    drop: function(props, monitor, component) {
+        console.log("dropped on sublist")
+        return { component: component };
+    },
+
     hover: function(props, monitor, component) {
         var item = monitor.getItem();
 
@@ -147,7 +152,7 @@ var RoomSubList = React.createClass({
     findRoomTile: function(room) {        
         var index = this.state.sortedList.indexOf(room); 
         if (index >= 0) {
-            //console.log("found: room: " + room + " with id " + room.roomId);
+            // console.log("found: room: " + room.roomId + " with index " + index);
         }
         else {
             console.log("didn't find room");
@@ -210,14 +215,14 @@ var RoomSubList = React.createClass({
             // XXX: is it evil to pass in self as a prop to RoomTile?
             return (
                 <RoomTile
-                    room={room}
-                    roomSubList={self}
-                    key={room.roomId}
-                    collapsed={self.props.collapsed}
-                    selected={selected}
-                    unread={self.props.activityMap[room.roomId] === 1}
-                    highlight={self.props.activityMap[room.roomId] === 2}
-                    isInvite={self.props.label === 'Invites'} />
+                    room={ room }
+                    roomSubList={ self }
+                    key={ room.roomId }
+                    collapsed={ self.props.collapsed }
+                    selected={ selected }
+                    unread={ self.props.activityMap[room.roomId] === 1 }
+                    highlight={ self.props.activityMap[room.roomId] === 2 }
+                    isInvite={ self.props.label === 'Invites' } />
             );
         });
     },