Move TagTile to separate file, and make it dragable

This commit is contained in:
Luke Barnard 2017-12-06 11:25:46 +00:00
parent 82a95f0793
commit a8a650c24a
4 changed files with 183 additions and 69 deletions

View file

@ -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",

View file

@ -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 ?
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
<div />;
return <AccessibleButton className={className} onClick={this.onClick}>
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
<BaseAvatar name={name} url={httpUrl} width={avatarHeight} height={avatarHeight} />
{ tip }
</div>
</AccessibleButton>;
},
});
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 <TagTile
return <DNDTagTile
key={groupProfile.groupId + '_' + index}
groupProfile={groupProfile}
selected={this.state.selectedTags.includes(groupProfile.groupId)}
@ -189,3 +126,4 @@ export default React.createClass({
</div>;
},
});
export default DragDropContext(HTML5Backend)(TagPanel);

View file

@ -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(
<div>
<TagTile {...otherProps} />
</div>,
));
}));

View file

@ -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 ?
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
<div />;
return <AccessibleButton className={className} onClick={this.onClick}>
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
<BaseAvatar name={name} url={httpUrl} width={avatarHeight} height={avatarHeight} />
{ tip }
</div>
</AccessibleButton>;
},
});