68 changed files with 357 additions and 869 deletions
@ -1,274 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|
||||
///
|
|
||||
/// 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 { AfterViewInit, Directive, ElementRef, EventEmitter, HostListener, Output } from '@angular/core'; |
|
||||
import { MatChipGrid, MatChipRow } from '@angular/material/chips'; |
|
||||
import Timeout = NodeJS.Timeout; |
|
||||
|
|
||||
export interface MatChipDropEvent { |
|
||||
from: number; |
|
||||
to: number; |
|
||||
} |
|
||||
|
|
||||
@Directive({ |
|
||||
selector: 'mat-chip-grid[tb-chip-draggable]', |
|
||||
}) |
|
||||
export class MatChipDraggableDirective implements AfterViewInit { |
|
||||
|
|
||||
@Output() |
|
||||
chipDrop = new EventEmitter<MatChipDropEvent>(); |
|
||||
|
|
||||
private draggableChips: Array<DraggableChip> = []; |
|
||||
|
|
||||
constructor(private chipsList: MatChipGrid, |
|
||||
private elementRef: ElementRef<HTMLElement>) { |
|
||||
} |
|
||||
|
|
||||
@HostListener('document:mouseup') |
|
||||
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 globalDraggingChipList: MatChipGrid = null; |
|
||||
|
|
||||
class DraggableChip { |
|
||||
|
|
||||
private chipElement: HTMLElement; |
|
||||
private readonly handle: HTMLElement; |
|
||||
|
|
||||
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: MatChipRow, |
|
||||
private chipsList: MatChipGrid, |
|
||||
private chipListElement: HTMLElement, |
|
||||
private chipDrop: EventEmitter<MatChipDropEvent>) { |
|
||||
chip._mousedown = () => {}; |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private onDragStart(event: Event | any) { |
|
||||
if (this.preventDrag) { |
|
||||
event.preventDefault(); |
|
||||
} else { |
|
||||
event.stopPropagation(); |
|
||||
this.dragging = true; |
|
||||
globalDraggingChipList = this.chipsList; |
|
||||
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'; |
|
||||
dataTransfer.setData('text', this.index() + ''); |
|
||||
const offset = this.calculateDragImageOffset(event, this.chipElement) || {x: 0, y: 0}; |
|
||||
(event.dataTransfer as any).setDragImage( this.chipElement, offset.x, offset.y ); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private onDragEnter(event: Event | any) { |
|
||||
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); |
|
||||
} |
|
||||
|
|
||||
private onDragLeave(event: Event | any) { |
|
||||
this.counter--; |
|
||||
if (this.counter <= 0) { |
|
||||
this.counter = 0; |
|
||||
this.chipElement.classList.remove(droppingClassName); |
|
||||
this.chipElement.classList.remove(droppingAfterClassName); |
|
||||
this.chipElement.classList.remove(droppingBeforeClassName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private onDragEnd(event: Event | any) { |
|
||||
event.stopPropagation(); |
|
||||
this.dragging = false; |
|
||||
globalDraggingChipList = null; |
|
||||
this.chipListElement.classList.remove(draggingClassName); |
|
||||
this.chipElement.classList.remove(draggingClassName); |
|
||||
} |
|
||||
|
|
||||
private onDragOver(event: Event | any) { |
|
||||
if (this.dragging) { |
|
||||
return; |
|
||||
} |
|
||||
event.preventDefault(); |
|
||||
if (globalDraggingChipList !== this.chipsList) { |
|
||||
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); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
private onDrop(event: Event | any) { |
|
||||
this.counter = 0; |
|
||||
event.preventDefault(); |
|
||||
if (globalDraggingChipList !== this.chipsList) { |
|
||||
return; |
|
||||
} |
|
||||
event = (event as any).originalEvent || event; |
|
||||
const droppedItemIndex = parseInt(event.dataTransfer.getData('text'), 10); |
|
||||
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); |
|
||||
} |
|
||||
if (droppedItemIndex !== newIndex) { |
|
||||
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); |
|
||||
} |
|
||||
|
|
||||
private calculateDragImageOffset(event: DragEvent, dragImage: Element): { x: number; y: number } { |
|
||||
|
|
||||
const dragImageComputedStyle = window.getComputedStyle( dragImage ); |
|
||||
const paddingTop = parseFloat( dragImageComputedStyle.paddingTop ) || 0; |
|
||||
const paddingLeft = parseFloat( dragImageComputedStyle.paddingLeft ) || 0; |
|
||||
const borderTop = parseFloat( dragImageComputedStyle.borderTopWidth ) || 0; |
|
||||
const borderLeft = parseFloat( dragImageComputedStyle.borderLeftWidth ) || 0; |
|
||||
|
|
||||
return { |
|
||||
x: event.offsetX + paddingLeft + borderLeft, |
|
||||
y: event.offsetY + paddingTop + borderTop |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
Loading…
Reference in new issue