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