diff --git a/package.json b/package.json
index 5c81db2153..943c443c59 100644
--- a/package.json
+++ b/package.json
@@ -77,6 +77,8 @@
"querystring": "^0.2.0",
"react": "^15.4.0",
"react-addons-css-transition-group": "15.3.2",
+ "react-dnd": "^2.1.4",
+ "react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.4.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
"sanitize-html": "^1.14.1",
diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js
index a853a59212..2c498e9eee 100644
--- a/src/components/structures/TagPanel.js
+++ b/src/components/structures/TagPanel.js
@@ -15,82 +15,17 @@ limitations under the License.
*/
import React from 'react';
+import { DragDropContext } from 'react-dnd';
+import HTML5Backend from 'react-dnd-html5-backend';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
-import classNames from 'classnames';
import FilterStore from '../../stores/FilterStore';
import FlairStore from '../../stores/FlairStore';
import TagOrderStore from '../../stores/TagOrderStore';
import sdk from '../../index';
import dis from '../../dispatcher';
-import { isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
-const TagTile = React.createClass({
- displayName: 'TagTile',
-
- propTypes: {
- groupProfile: PropTypes.object,
- },
-
- contextTypes: {
- matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
- },
-
- getInitialState() {
- return {
- hover: false,
- };
- },
-
- onClick: function(e) {
- e.preventDefault();
- e.stopPropagation();
- dis.dispatch({
- action: 'select_tag',
- tag: this.props.groupProfile.groupId,
- ctrlOrCmdKey: isOnlyCtrlOrCmdKeyEvent(e),
- shiftKey: e.shiftKey,
- });
- },
-
- onMouseOver: function() {
- this.setState({hover: true});
- },
-
- onMouseOut: function() {
- this.setState({hover: false});
- },
-
- render: function() {
- const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
- const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
- const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
- const profile = this.props.groupProfile || {};
- const name = profile.name || profile.groupId;
- const avatarHeight = 35;
-
- const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
- profile.avatarUrl, avatarHeight, avatarHeight, "crop",
- ) : null;
-
- const className = classNames({
- mx_TagTile: true,
- mx_TagTile_selected: this.props.selected,
- });
-
- const tip = this.state.hover ?
- :
-
;
- return
-
-
- { tip }
-
- ;
- },
-});
-
-export default React.createClass({
+const TagPanel = React.createClass({
displayName: 'TagPanel',
contextTypes: {
@@ -166,14 +101,16 @@ export default React.createClass({
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg');
+ const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
const orderedGroupProfiles = this.state.orderedTags ?
this.state.joinedGroupProfiles.sort((a, b) =>
this.state.orderedTags.indexOf(a.groupId) -
this.state.orderedTags.indexOf(b.groupId),
) : this.state.joinedGroupProfiles;
+
const tags = orderedGroupProfiles.map((groupProfile, index) => {
- return ;
},
});
+export default DragDropContext(HTML5Backend)(TagPanel);
diff --git a/src/components/views/elements/DNDTagTile.js b/src/components/views/elements/DNDTagTile.js
new file mode 100644
index 0000000000..6715bd8dd3
--- /dev/null
+++ b/src/components/views/elements/DNDTagTile.js
@@ -0,0 +1,86 @@
+/*
+Copyright 2017 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 { DragSource, DropTarget } from 'react-dnd';
+
+import TagTile from './TagTile';
+import dis from '../../../dispatcher';
+import { findDOMNode } from 'react-dom';
+
+const tagTileSource = {
+ canDrag: function(props, monitor) {
+ return true;
+ },
+
+ beginDrag: function(props) {
+ // Return the data describing the dragged item
+ return {
+ tag: props.groupProfile.groupId,
+ };
+ },
+
+ endDrag: function(props, monitor, component) {
+ const dropResult = monitor.getDropResult();
+ if (!monitor.didDrop() || !dropResult) {
+ return;
+ }
+ dis.dispatch({
+ action: 'commit_tags',
+ });
+ },
+};
+
+const tagTileTarget = {
+ canDrop(props, monitor) {
+ return true;
+ },
+
+ hover(props, monitor, component) {
+ if (!monitor.canDrop()) return;
+ const draggedY = monitor.getClientOffset().y;
+ const {top, bottom} = findDOMNode(component).getBoundingClientRect();
+ const targetY = (top + bottom) / 2;
+ dis.dispatch({
+ action: 'order_tag',
+ tag: monitor.getItem().tag,
+ targetTag: props.groupProfile.groupId,
+ // Note: we indicate that the tag should be after the target when
+ // it's being dragged over the top half of the target.
+ after: draggedY < targetY,
+ });
+ },
+
+ drop(props) {
+ // Return the data to be returned by getDropResult
+ return {
+ tag: props.groupProfile.groupId,
+ };
+ },
+};
+
+export default
+ DropTarget('TagTile', tagTileTarget, (connect, monitor) => ({
+ connectDropTarget: connect.dropTarget(),
+ }))(DragSource('TagTile', tagTileSource, (connect, monitor) => ({
+ connectDragSource: connect.dragSource(),
+ }))((props) => {
+ const { connectDropTarget, connectDragSource, ...otherProps } = props;
+ return connectDropTarget(connectDragSource(
+
+
+
,
+ ));
+ }));
diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js
new file mode 100644
index 0000000000..124559a838
--- /dev/null
+++ b/src/components/views/elements/TagTile.js
@@ -0,0 +1,88 @@
+/*
+Copyright 2017 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 classNames from 'classnames';
+import { MatrixClient } from 'matrix-js-sdk';
+import sdk from '../../../index';
+import dis from '../../../dispatcher';
+import { isOnlyCtrlOrCmdKeyEvent } from '../../../Keyboard';
+
+export default React.createClass({
+ displayName: 'TagTile',
+
+ propTypes: {
+ groupProfile: PropTypes.object,
+ },
+
+ contextTypes: {
+ matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
+ },
+
+ getInitialState() {
+ return {
+ hover: false,
+ };
+ },
+
+ onClick: function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ dis.dispatch({
+ action: 'select_tag',
+ tag: this.props.groupProfile.groupId,
+ ctrlOrCmdKey: isOnlyCtrlOrCmdKeyEvent(e),
+ shiftKey: e.shiftKey,
+ });
+ },
+
+ onMouseOver: function() {
+ this.setState({hover: true});
+ },
+
+ onMouseOut: function() {
+ this.setState({hover: false});
+ },
+
+ render: function() {
+ const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
+ const profile = this.props.groupProfile || {};
+ const name = profile.name || profile.groupId;
+ const avatarHeight = 35;
+
+ const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
+ profile.avatarUrl, avatarHeight, avatarHeight, "crop",
+ ) : null;
+
+ const className = classNames({
+ mx_TagTile: true,
+ mx_TagTile_selected: this.props.selected,
+ });
+
+ const tip = this.state.hover ?
+ :
+ ;
+ return
+
+
+ { tip }
+
+ ;
+ },
+});