Tag component drag reorder

pull/1297/head
Piero Toffanin 2023-02-23 15:13:00 -05:00
rodzic e6c423f240
commit de79e1b606
6 zmienionych plików z 157 dodań i 11 usunięć

Wyświetl plik

@ -621,8 +621,8 @@ class EditTaskForm extends React.Component {
placeholder={this.state.namePlaceholder}
value={this.state.name}
/>
<button type="button" title={_("Add tags")} onClick={this.toggleTagsField} class="btn btn-sm btn-secondary toggle-tags">
<i class="fa fa-tag"></i>
<button type="button" title={_("Add tags")} onClick={this.toggleTagsField} className="btn btn-sm btn-secondary toggle-tags">
<i className="fa fa-tag"></i>
</button>
</div>

Wyświetl plik

@ -6,7 +6,7 @@ import { _ } from '../classes/gettext';
class TagsField extends React.Component {
static defaultProps = {
tags: ["abc"]
tags: ["abc", "123", "xyz", "aaaaaaaaaaaaaaaa", "bbbbbbbbbbbb", "ccccccccccc", "dddddddddddd"]
};
static propTypes = {
@ -19,6 +19,36 @@ class TagsField extends React.Component {
this.state = {
tags: props.tags
}
this.dzList = [];
this.domTags = [];
}
componentWillUnmount(){
this.restoreDropzones();
}
disableDropzones(){
if (this.disabledDz) return;
let parent = this.domNode.parentElement;
while(parent){
if (parent.dropzone){
parent.dropzone.removeListeners();
this.dzList.push(parent.dropzone);
}
parent = parent.parentElement;
}
this.disabledDz = true;
}
restoreDropzones(){
if (!this.disabledDz) return;
this.dzList.forEach(dz => {
dz.restoreListeners();
});
this.dzList = [];
this.disabledDz = false;
}
handleKeyDown = e => {
@ -58,15 +88,115 @@ class TagsField extends React.Component {
}
}
handleDragStart = tag => {
return e => {
this.disableDropzones();
e.stopPropagation();
e.dataTransfer.setData("application/tag", tag);
e.dataTransfer.dropEffect = "move";
}
}
handleDrop = e => {
e.preventDefault();
const dragTag = e.dataTransfer.getData("application/tag");
const [moveTag, side] = this.findClosestTag(e.clientX, e.clientY);
const { tags } = this.state;
if (moveTag){
const dragIdx = tags.indexOf(dragTag);
const moveIdx = tags.indexOf(moveTag);
if (dragIdx !== -1 && moveIdx !== -1){
if (dragIdx === moveIdx) return;
else{
// Put drag tag in front of move tag
let insertIdx = side === "right" ? moveIdx + 1 : moveIdx;
tags.splice(insertIdx, 0, dragTag);
for (let i = 0; i < tags.length; i++){
if (tags[i] === dragTag && i !== insertIdx){
tags.splice(i, 1);
break;
}
}
this.setState({tags});
}
}
}
}
handleDragOver = e => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
}
handleDragEnter = e => {
e.preventDefault();
}
handleDragEnd = () => {
this.restoreDropzones();
}
findClosestTag = (clientX, clientY) => {
let closestTag = null;
let minDistX = Infinity, minDistY = Infinity;
let rowTagY = null;
const { tags } = this.state;
const row = [];
// Find tags in closest row
this.domTags.forEach((domTag, i) => {
const b = domTag.getBoundingClientRect();
const tagY = b.y + (b.height / 2);
let dy = clientY - tagY,
sqDistY = dy*dy;
if (sqDistY < minDistY){
minDistY = sqDistY;
rowTagY = tagY;
}
});
if (!rowTagY) return [null, ""];
// From row, find closest in X
this.domTags.forEach((domTag, i) => {
const b = domTag.getBoundingClientRect();
const tagY = b.y + (b.height / 2);
if (Math.abs(tagY - rowTagY) < 0.001){
const tagX = b.x + b.width;
let dx = clientX - tagX,
sqDistX = dx*dx;
if (sqDistX < minDistX){
closestTag = tags[i];
minDistX = sqDistX;
}
}
});
let side = "right";
if (closestTag){
const b = this.domTags[this.state.tags.indexOf(closestTag)].getBoundingClientRect();
const centerX = b.x + b.width / 2.0;
if (clientX < centerX) side = "left";
}
return [closestTag, side];
}
render() {
return (<div
spellcheck="false"
autocomplete="off"
ref={domNode => this.domNode = domNode}
spellCheck="false"
autoComplete="off"
onClick={this.focus}
onDrop={this.handleDrop}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
className="form-control tags-field">{this.state.tags.map((tag, i) =>
<div className="tag-badge" key={i} onClick={this.stop}>{tag} <a href="javascript:void(0)" onClick={this.removeTag(i)}>×</a>&nbsp;&nbsp;</div>
<div draggable="true" className="tag-badge" key={i} ref={domNode => this.domTags[i] = domNode}
onClick={this.stop}
onDragStart={this.handleDragStart(tag)}
onDragEnd={this.handleDragEnd}>{tag} <a href="javascript:void(0)" onClick={this.removeTag(i)}>×</a>&nbsp;&nbsp;</div>
)}
<div className="inputText" contenteditable="true" ref={(domNode) => this.inputText = domNode}
<div className="inputText" contentEditable="true" ref={(domNode) => this.inputText = domNode}
onKeyDown={this.handleKeyDown}
onBlur={this.addTag}></div>
</div>);

Wyświetl plik

@ -90,8 +90,6 @@ class TaskList extends React.Component {
return (
<div className="task-list">
{message}
{this.state.tasks.map(task => (
<TaskListItem
data={task}
@ -103,6 +101,8 @@ class TaskList extends React.Component {
hasPermission={this.props.hasPermission}
history={this.props.history} />
))}
{message}
</div>
);
}

Wyświetl plik

@ -28,7 +28,7 @@
.name-loading{
position: absolute;
right: 30px;
right: 60px;
top: 15px;
opacity: 0.5;
}

Wyświetl plik

@ -7,7 +7,7 @@
}
.tag-badge{
&:hover{
cursor: default;
cursor: grab;
}
display: inline-block;
width: auto;

Wyświetl plik

@ -1555,6 +1555,22 @@ var Dropzone = function (_Emitter) {
return _this4.cancelUpload(file);
});
}
}, {
key: "removeListeners",
value: function disable() {
this.clickableElements.forEach(function (element) {
return element.classList.remove("dz-clickable");
});
this.removeEventListeners();
}
}, {
key: "restoreListeners",
value: function disable() {
this.clickableElements.forEach(function (element) {
return element.classList.add("dz-clickable");
});
return this.setupEventListeners();
}
}, {
key: "enable",
value: function enable() {