2019-10-17 18:23:53 +03:00
|
|
|
///
|
2020-02-20 10:26:43 +02:00
|
|
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
2019-10-17 18:23:53 +03:00
|
|
|
///
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
|
2020-02-10 19:06:15 +02:00
|
|
|
import { AfterViewInit, Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core';
|
2019-10-17 18:23:53 +03:00
|
|
|
import { MatChip, MatChipList } from '@angular/material/chips';
|
|
|
|
|
import Timeout = NodeJS.Timeout;
|
|
|
|
|
|
|
|
|
|
export interface MatChipDropEvent {
|
|
|
|
|
from: number;
|
|
|
|
|
to: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Directive({
|
|
|
|
|
selector: 'mat-chip-list[tb-chip-draggable]',
|
|
|
|
|
})
|
|
|
|
|
export class MatChipDraggableDirective implements AfterViewInit {
|
|
|
|
|
|
|
|
|
|
@Output()
|
|
|
|
|
chipDrop = new EventEmitter<MatChipDropEvent>();
|
|
|
|
|
|
|
|
|
|
private draggableChips: Array<DraggableChip> = [];
|
|
|
|
|
|
|
|
|
|
constructor(private chipsList: MatChipList,
|
|
|
|
|
private elementRef: ElementRef<HTMLElement>) {
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 14:36:44 +02:00
|
|
|
@HostListener('document:mouseup')
|
2019-10-17 18:23:53 +03:00
|
|
|
onDocumentMouseUp() {
|
|
|
|
|
this.draggableChips.forEach((draggableChip) => {
|
|
|
|
|
draggableChip.preventDrag = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngAfterViewInit(): void {
|
|
|
|
|
this.configureDraggableChipList();
|
|
|
|
|
this.chipsList.chips.changes.subscribe(() => {
|
|
|
|
|
this.configureDraggableChipList();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private configureDraggableChipList() {
|
|
|
|
|
const toRemove: Array<DraggableChip> = [];
|
|
|
|
|
this.chipsList.chips.forEach((chip) => {
|
|
|
|
|
const found = this.draggableChips.find((draggableChip) => draggableChip.chip === chip);
|
|
|
|
|
if (!found) {
|
|
|
|
|
this.draggableChips.push(new DraggableChip(chip,
|
|
|
|
|
this.chipsList,
|
|
|
|
|
this.elementRef.nativeElement,
|
|
|
|
|
this.chipDrop));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
this.draggableChips.forEach((draggableChip) => {
|
|
|
|
|
const found = this.chipsList.chips.find((chip) => chip === draggableChip.chip);
|
|
|
|
|
if (!found) {
|
|
|
|
|
toRemove.push(draggableChip);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
toRemove.forEach((draggableChip) => {
|
|
|
|
|
const index = this.draggableChips.indexOf(draggableChip);
|
|
|
|
|
this.draggableChips.splice(index, 1);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const draggingClassName = 'dragging';
|
|
|
|
|
const droppingClassName = 'dropping';
|
|
|
|
|
const droppingBeforeClassName = 'dropping-before';
|
|
|
|
|
const droppingAfterClassName = 'dropping-after';
|
|
|
|
|
|
|
|
|
|
let globalDraggingChipListId = null;
|
|
|
|
|
|
|
|
|
|
class DraggableChip {
|
|
|
|
|
|
|
|
|
|
private chipElement: HTMLElement;
|
2020-02-04 15:14:17 +02:00
|
|
|
private readonly handle: HTMLElement;
|
2019-10-17 18:23:53 +03:00
|
|
|
|
|
|
|
|
private dragging = false;
|
|
|
|
|
private counter = 0;
|
|
|
|
|
|
|
|
|
|
private dropPosition: 'after' | 'before';
|
|
|
|
|
|
|
|
|
|
private dropTimeout: Timeout;
|
|
|
|
|
|
|
|
|
|
public preventDrag = false;
|
|
|
|
|
|
|
|
|
|
private dropHandler = this.onDrop.bind(this);
|
|
|
|
|
private dragOverHandler = this.onDragOver.bind(this);
|
|
|
|
|
|
|
|
|
|
constructor(public chip: MatChip,
|
|
|
|
|
private chipsList: MatChipList,
|
|
|
|
|
private chipListElement: HTMLElement,
|
|
|
|
|
private chipDrop: EventEmitter<MatChipDropEvent>) {
|
|
|
|
|
this.chipElement = chip._elementRef.nativeElement;
|
|
|
|
|
this.chipElement.setAttribute('draggable', 'true');
|
|
|
|
|
this.handle = this.chipElement.getElementsByClassName('tb-chip-drag-handle')[0] as HTMLElement;
|
|
|
|
|
this.chipElement.addEventListener('mousedown', this.onMouseDown.bind(this));
|
|
|
|
|
this.chipElement.addEventListener('dragstart', this.onDragStart.bind(this));
|
|
|
|
|
this.chipElement.addEventListener('dragend', this.onDragEnd.bind(this));
|
|
|
|
|
this.chipElement.addEventListener('dragenter', this.onDragEnter.bind(this));
|
|
|
|
|
this.chipElement.addEventListener('dragleave', this.onDragLeave.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onMouseDown(event: MouseEvent) {
|
|
|
|
|
if (event.target !== this.handle) {
|
|
|
|
|
this.preventDrag = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDragStart(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
if (this.preventDrag) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
} else {
|
|
|
|
|
this.dragging = true;
|
|
|
|
|
globalDraggingChipListId = this.chipListElement.id;
|
|
|
|
|
this.chipListElement.classList.add(draggingClassName);
|
|
|
|
|
this.chipElement.classList.add(draggingClassName);
|
|
|
|
|
event = (event as any).originalEvent || event;
|
|
|
|
|
const dataTransfer = event.dataTransfer;
|
|
|
|
|
dataTransfer.effectAllowed = 'copyMove';
|
|
|
|
|
dataTransfer.dropEffect = 'move';
|
2019-10-31 18:33:51 +02:00
|
|
|
dataTransfer.setData('text', this.index() + '');
|
2019-10-17 18:23:53 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDragEnter(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
this.counter++;
|
|
|
|
|
if (this.dragging) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.chipElement.removeEventListener('dragover', this.dragOverHandler);
|
|
|
|
|
this.chipElement.removeEventListener('drop', this.dropHandler);
|
|
|
|
|
|
|
|
|
|
this.chipElement.addEventListener('dragover', this.dragOverHandler);
|
|
|
|
|
this.chipElement.addEventListener('drop', this.dropHandler);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDragLeave(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
this.counter--;
|
|
|
|
|
if (this.counter <= 0) {
|
|
|
|
|
this.counter = 0;
|
|
|
|
|
this.chipElement.classList.remove(droppingClassName);
|
|
|
|
|
this.chipElement.classList.remove(droppingAfterClassName);
|
|
|
|
|
this.chipElement.classList.remove(droppingBeforeClassName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDragEnd(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
this.dragging = false;
|
|
|
|
|
globalDraggingChipListId = null;
|
|
|
|
|
this.chipListElement.classList.remove(draggingClassName);
|
|
|
|
|
this.chipElement.classList.remove(draggingClassName);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDragOver(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
if (this.dragging) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
if (globalDraggingChipListId !== this.chipListElement.id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const bounds = this.chipElement.getBoundingClientRect();
|
|
|
|
|
event = (event as any).originalEvent || event;
|
|
|
|
|
const props = {
|
|
|
|
|
width: bounds.right - bounds.left,
|
|
|
|
|
height: bounds.bottom - bounds.top,
|
|
|
|
|
x: event.clientX - bounds.left,
|
|
|
|
|
y: event.clientY - bounds.top,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const horizontalOffset = props.x;
|
|
|
|
|
const horizontalMidPoint = props.width / 2;
|
|
|
|
|
|
|
|
|
|
const verticalOffset = props.y;
|
|
|
|
|
const verticalMidPoint = props.height / 2;
|
|
|
|
|
|
|
|
|
|
this.chipElement.classList.add(droppingClassName);
|
|
|
|
|
|
|
|
|
|
this.chipElement.classList.remove(droppingAfterClassName);
|
|
|
|
|
this.chipElement.classList.remove(droppingBeforeClassName);
|
|
|
|
|
|
|
|
|
|
if (horizontalOffset >= horizontalMidPoint || verticalOffset >= verticalMidPoint) {
|
|
|
|
|
this.dropPosition = 'after';
|
|
|
|
|
this.chipElement.classList.add(droppingAfterClassName);
|
|
|
|
|
} else {
|
|
|
|
|
this.dropPosition = 'before';
|
|
|
|
|
this.chipElement.classList.add(droppingBeforeClassName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-01 09:47:26 +03:00
|
|
|
private onDrop(event: Event | any) {
|
2019-10-17 18:23:53 +03:00
|
|
|
this.counter = 0;
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
if (globalDraggingChipListId !== this.chipListElement.id) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
event = (event as any).originalEvent || event;
|
2019-10-31 18:33:51 +02:00
|
|
|
const droppedItemIndex = parseInt(event.dataTransfer.getData('text'), 10);
|
2019-10-17 18:23:53 +03:00
|
|
|
const currentIndex = this.index();
|
|
|
|
|
let newIndex;
|
|
|
|
|
if (this.dropPosition === 'before') {
|
|
|
|
|
if (droppedItemIndex < currentIndex) {
|
|
|
|
|
newIndex = currentIndex - 1;
|
|
|
|
|
} else {
|
|
|
|
|
newIndex = currentIndex;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (droppedItemIndex < currentIndex) {
|
|
|
|
|
newIndex = currentIndex;
|
|
|
|
|
} else {
|
|
|
|
|
newIndex = currentIndex + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (this.dropTimeout) {
|
|
|
|
|
clearTimeout(this.dropTimeout);
|
|
|
|
|
}
|
|
|
|
|
this.dropTimeout = setTimeout(() => {
|
|
|
|
|
this.dropPosition = null;
|
|
|
|
|
|
|
|
|
|
this.chipElement.classList.remove(droppingClassName);
|
|
|
|
|
this.chipElement.classList.remove(droppingAfterClassName);
|
|
|
|
|
this.chipElement.classList.remove(droppingBeforeClassName);
|
|
|
|
|
|
|
|
|
|
this.chipElement.removeEventListener('drop', this.dropHandler);
|
|
|
|
|
|
|
|
|
|
const dropEvent: MatChipDropEvent = {
|
|
|
|
|
from: droppedItemIndex,
|
|
|
|
|
to: newIndex
|
|
|
|
|
};
|
|
|
|
|
this.chipDrop.emit(dropEvent);
|
|
|
|
|
}, 1000 / 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private index(): number {
|
|
|
|
|
return this.chipsList.chips.toArray().indexOf(this.chip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|