Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap RoomList to react-beautiful-dnd #6008

Merged
merged 4 commits into from
Jan 19, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
38 changes: 19 additions & 19 deletions src/components/structures/RoomSubList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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;

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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 (
<DNDRoomTile
index={index} // For DND
room={ room }
roomSubList={ self }
tagName={self.props.tagName}
key={ room.roomId }
collapsed={ self.props.collapsed || false}
unread={ Unread.doesRoomHaveUnreadMessages(room) }
Expand Down Expand Up @@ -566,12 +567,18 @@ var RoomSubList = React.createClass({
</TruncatedList>;
}

return connectDropTarget(
<div>
{ this._getHeaderJsx() }
{ subList }
</div>
);
const subListContent = <div>
{ this._getHeaderJsx() }
{ subList }
</div>;

return this.props.editable ? <Droppable droppableId={"room-sub-list-droppable_" + this.props.tagName}>
{ (provided, snapshot) => (
<div ref={provided.innerRef}>
{ subListContent }
</div>
) }
</Droppable> : subListContent;
}
else {
var Loader = sdk.getComponent("elements.Spinner");
Expand All @@ -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;
255 changes: 39 additions & 216 deletions src/components/views/rooms/DNDRoomTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
*/

/**
* Specifies the drag source contract.
* Only `beginDrag` function is required.
*/
var roomTileSource = {
canDrag: function(props, monitor) {
return props.roomSubList.props.editable;
},
export default class DNDRoomTile extends React.Component {
constructor() {
super();
this.getStyle = this.getStyle.bind(this);
}

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we apply this style via a CSS class rather than hardcoding it in here, in order to make it skinnable/themable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why not, actually. I was persuaded by the library examples that all did it this way but there's no obvious advantage.

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;
},

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(),
render() {
const props = this.props;

return <div>
<Draggable
key={props.room.roomId}
draggableId={props.tagName + '_' + props.room.roomId}
index={props.index}
>
{ (provided, snapshot) => {
return (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div style={this.getStyle(snapshot.isDragging)}>
<RoomTile {...props} />
</div>
</div>
{ provided.placeholder }
</div>
);
} }
</Draggable>
</div>;
}
})(
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));
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ limitations under the License.
font-size: 13px;
display: block;
height: 34px;

background-color: $secondary-accent-color;
}

.mx_RoomTile_tooltip {
Expand Down
1 change: 1 addition & 0 deletions src/skins/vector/css/themes/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/skins/vector/css/themes/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading