You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
13 KiB
TypeScript
342 lines
13 KiB
TypeScript
import { FcModelService } from './model.service';
|
|
import { FcCoords, FcNode, FlowchartConstants } from './ngx-flowchart.models';
|
|
|
|
const nodeDropScope: NodeDropScope = {
|
|
dropElement: null
|
|
};
|
|
|
|
export class FcNodeDraggingService {
|
|
|
|
nodeDraggingScope: NodeDraggingScope = {
|
|
shadowDragStarted: false,
|
|
dropElement: null,
|
|
draggedNodes: [],
|
|
shadowElements: []
|
|
};
|
|
|
|
private dragOffsets: FcCoords[] = [];
|
|
private draggedElements: HTMLElement[] = [];
|
|
|
|
private destinationHtmlElements: HTMLElement[] = [];
|
|
private oldDisplayStyles: string[] = [];
|
|
|
|
private readonly modelService: FcModelService;
|
|
private readonly automaticResize: boolean;
|
|
private readonly dragAnimation: string;
|
|
private readonly applyFunction: <T>(fn: (...args: any[]) => T) => T;
|
|
|
|
constructor(modelService: FcModelService,
|
|
applyFunction: <T>(fn: (...args: any[]) => T) => T,
|
|
automaticResize: boolean, dragAnimation: string) {
|
|
this.modelService = modelService;
|
|
this.automaticResize = automaticResize;
|
|
this.dragAnimation = dragAnimation;
|
|
this.applyFunction = applyFunction;
|
|
}
|
|
|
|
private getCoordinate(coordinate: number, max: number): number {
|
|
coordinate = Math.max(coordinate, 0);
|
|
coordinate = Math.min(coordinate, max);
|
|
return coordinate;
|
|
}
|
|
|
|
private getXCoordinate(x: number): number {
|
|
return this.getCoordinate(x, this.modelService.canvasHtmlElement.offsetWidth);
|
|
}
|
|
|
|
private getYCoordinate(y: number): number {
|
|
return this.getCoordinate(y, this.modelService.canvasHtmlElement.offsetHeight);
|
|
}
|
|
|
|
private resizeCanvas(draggedNode: FcNode, nodeElement: HTMLElement) {
|
|
if (this.automaticResize && !this.modelService.isDropSource()) {
|
|
const canvasElement = this.modelService.canvasHtmlElement;
|
|
if (canvasElement.offsetWidth < draggedNode.x + nodeElement.offsetWidth + FlowchartConstants.canvasResizeThreshold) {
|
|
canvasElement.style.width = canvasElement.offsetWidth + FlowchartConstants.canvasResizeStep + 'px';
|
|
}
|
|
if (canvasElement.offsetHeight < draggedNode.y + nodeElement.offsetHeight + FlowchartConstants.canvasResizeThreshold) {
|
|
canvasElement.style.height = canvasElement.offsetHeight + FlowchartConstants.canvasResizeStep + 'px';
|
|
}
|
|
}
|
|
}
|
|
|
|
public isDraggingNode(node: FcNode): boolean {
|
|
return this.nodeDraggingScope.draggedNodes.includes(node);
|
|
}
|
|
|
|
public dragstart(event: Event | any, node: FcNode) {
|
|
if (node.readonly) {
|
|
return;
|
|
}
|
|
this.dragOffsets.length = 0;
|
|
this.draggedElements.length = 0;
|
|
this.nodeDraggingScope.draggedNodes.length = 0;
|
|
this.nodeDraggingScope.shadowElements.length = 0;
|
|
this.destinationHtmlElements.length = 0;
|
|
this.oldDisplayStyles.length = 0;
|
|
const elements: Array<JQuery<HTMLElement>> = [];
|
|
const nodes: Array<FcNode> = [];
|
|
if (this.modelService.nodes.isSelected(node)) {
|
|
const selectedNodes = this.modelService.nodes.getSelectedNodes();
|
|
for (const selectedNode of selectedNodes) {
|
|
const element = $(this.modelService.nodes.getHtmlElement(selectedNode.id));
|
|
elements.push(element);
|
|
nodes.push(selectedNode);
|
|
}
|
|
} else {
|
|
elements.push($(event.target as HTMLElement));
|
|
nodes.push(node);
|
|
}
|
|
const offsetsX: number[] = [];
|
|
const offsetsY: number[] = [];
|
|
for (const element of elements) {
|
|
offsetsX.push(parseInt(element.css('left'), 10) - event.clientX);
|
|
offsetsY.push(parseInt(element.css('top'), 10) - event.clientY);
|
|
}
|
|
const originalEvent: Event | any = (event as any).originalEvent || event;
|
|
if (this.modelService.isDropSource()) {
|
|
if (nodeDropScope.dropElement) {
|
|
nodeDropScope.dropElement.parentNode.removeChild(nodeDropScope.dropElement);
|
|
nodeDropScope.dropElement = null;
|
|
}
|
|
nodeDropScope.dropElement = elements[0][0].cloneNode(true) as NodeDropElement;
|
|
const offset = $(this.modelService.canvasHtmlElement).offset();
|
|
nodeDropScope.dropElement.offsetInfo = {
|
|
offsetX: Math.round(offsetsX[0] + offset.left),
|
|
offsetY: Math.round(offsetsY[0] + offset.top)
|
|
};
|
|
nodeDropScope.dropElement.style.position = 'absolute';
|
|
nodeDropScope.dropElement.style.pointerEvents = 'none';
|
|
nodeDropScope.dropElement.style.zIndex = '9999';
|
|
|
|
document.body.appendChild(nodeDropScope.dropElement);
|
|
const dropNodeInfo: DropNodeInfo = {
|
|
node,
|
|
dropTargetId: this.modelService.dropTargetId,
|
|
offsetX: Math.round(offsetsX[0] + offset.left),
|
|
offsetY: Math.round(offsetsY[0] + offset.top)
|
|
};
|
|
originalEvent.dataTransfer.setData('text', JSON.stringify(dropNodeInfo));
|
|
|
|
if (originalEvent.dataTransfer.setDragImage) {
|
|
originalEvent.dataTransfer.setDragImage(this.modelService.getDragImage(), 0, 0);
|
|
} else {
|
|
const target: HTMLElement = event.target as HTMLElement;
|
|
const cloneNode = target.cloneNode(true);
|
|
target.parentNode.insertBefore(cloneNode, target);
|
|
target.style.visibility = 'collapse';
|
|
setTimeout(() => {
|
|
target.parentNode.removeChild(cloneNode);
|
|
target.style.visibility = 'visible';
|
|
}, 0);
|
|
}
|
|
return;
|
|
}
|
|
this.nodeDraggingScope.draggedNodes = nodes;
|
|
for (let i = 0; i < elements.length; i++) {
|
|
this.draggedElements.push(elements[i][0]);
|
|
this.dragOffsets.push(
|
|
{
|
|
x: offsetsX[i],
|
|
y: offsetsY[i]
|
|
}
|
|
);
|
|
}
|
|
|
|
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
for (let i = 0; i < this.draggedElements.length; i++) {
|
|
const dragOffset = this.dragOffsets[i];
|
|
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
const shadowElement = $(`<div style="position: absolute; opacity: 0.7; ` +
|
|
`top: ${this.getYCoordinate(dragOffset.y + event.clientY)}px; ` +
|
|
`left: ${this.getXCoordinate(dragOffset.x + event.clientX)}px; ">` +
|
|
`<div class="innerNode"><p style="padding: 0 15px;">${draggedNode.name}</p> </div></div>`);
|
|
const targetInnerNode = $(this.draggedElements[i]).children()[0];
|
|
shadowElement.children()[0].style.backgroundColor = targetInnerNode.style.backgroundColor;
|
|
this.nodeDraggingScope.shadowElements.push(shadowElement);
|
|
this.modelService.canvasHtmlElement.appendChild(this.nodeDraggingScope.shadowElements[i][0]);
|
|
}
|
|
}
|
|
originalEvent.dataTransfer.setData('text', 'Just to support firefox');
|
|
if (originalEvent.dataTransfer.setDragImage) {
|
|
originalEvent.dataTransfer.setDragImage(this.modelService.getDragImage(), 0, 0);
|
|
} else {
|
|
this.draggedElements.forEach((draggedElement) => {
|
|
const cloneNode = draggedElement.cloneNode(true);
|
|
draggedElement.parentNode.insertBefore(cloneNode, draggedElement);
|
|
draggedElement.style.visibility = 'collapse';
|
|
setTimeout(() => {
|
|
draggedElement.parentNode.removeChild(cloneNode);
|
|
draggedElement.style.visibility = 'visible';
|
|
}, 0);
|
|
});
|
|
if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
for (let i = 0; i < this.draggedElements.length; i++) {
|
|
this.destinationHtmlElements.push(this.draggedElements[i]);
|
|
this.oldDisplayStyles.push(this.destinationHtmlElements[i].style.display);
|
|
this.destinationHtmlElements[i].style.display = 'none';
|
|
}
|
|
this.nodeDraggingScope.shadowDragStarted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public drop(event: Event | any): boolean {
|
|
if (this.modelService.isDropSource()) {
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
let dropNode: FcNode = null;
|
|
const originalEvent: Event | any = (event as any).originalEvent || event;
|
|
const infoText = originalEvent.dataTransfer.getData('text');
|
|
if (infoText) {
|
|
let dropNodeInfo: DropNodeInfo = null;
|
|
try {
|
|
dropNodeInfo = JSON.parse(infoText);
|
|
} catch (e) {}
|
|
if (dropNodeInfo && dropNodeInfo.dropTargetId) {
|
|
if (this.modelService.canvasHtmlElement.id &&
|
|
this.modelService.canvasHtmlElement.id === dropNodeInfo.dropTargetId) {
|
|
dropNode = dropNodeInfo.node;
|
|
const offset = $(this.modelService.canvasHtmlElement).offset();
|
|
const x = event.clientX - offset.left;
|
|
const y = event.clientY - offset.top;
|
|
dropNode.x = Math.round(this.getXCoordinate(dropNodeInfo.offsetX + x));
|
|
dropNode.y = Math.round(this.getYCoordinate(dropNodeInfo.offsetY + y));
|
|
}
|
|
}
|
|
}
|
|
if (dropNode) {
|
|
this.modelService.dropNode(event, dropNode);
|
|
event.preventDefault();
|
|
return false;
|
|
} else if (this.nodeDraggingScope.draggedNodes.length) {
|
|
return this.applyFunction(() => {
|
|
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
const dragOffset = this.dragOffsets[i];
|
|
draggedNode.x = Math.round(this.getXCoordinate(dragOffset.x + event.clientX));
|
|
draggedNode.y = Math.round(this.getYCoordinate(dragOffset.y + event.clientY));
|
|
}
|
|
event.preventDefault();
|
|
this.modelService.notifyModelChanged();
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
public dragover(event: Event | any) {
|
|
if (nodeDropScope.dropElement) {
|
|
const offsetInfo = nodeDropScope.dropElement.offsetInfo;
|
|
nodeDropScope.dropElement.style.left = (offsetInfo.offsetX + event.clientX) + 'px';
|
|
nodeDropScope.dropElement.style.top = (offsetInfo.offsetY + event.clientY) + 'px';
|
|
if (this.nodeDraggingScope.shadowDragStarted) {
|
|
this.applyFunction(() => {
|
|
this.destinationHtmlElements[0].style.display = this.oldDisplayStyles[0];
|
|
this.nodeDraggingScope.shadowDragStarted = false;
|
|
});
|
|
}
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
if (this.modelService.isDropSource()) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
if (!this.nodeDraggingScope.draggedNodes.length) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
if (this.dragAnimation === FlowchartConstants.dragAnimationRepaint) {
|
|
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
return this.applyFunction(() => {
|
|
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
const dragOffset = this.dragOffsets[i];
|
|
draggedNode.x = this.getXCoordinate(dragOffset.x + event.clientX);
|
|
draggedNode.y = this.getYCoordinate(dragOffset.y + event.clientY);
|
|
this.resizeCanvas(draggedNode, this.draggedElements[i]);
|
|
}
|
|
event.preventDefault();
|
|
this.modelService.notifyModelChanged();
|
|
return false;
|
|
});
|
|
}
|
|
} else if (this.dragAnimation === FlowchartConstants.dragAnimationShadow) {
|
|
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
if (this.nodeDraggingScope.shadowDragStarted) {
|
|
this.applyFunction(() => {
|
|
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
this.destinationHtmlElements[i].style.display = this.oldDisplayStyles[i];
|
|
}
|
|
this.nodeDraggingScope.shadowDragStarted = false;
|
|
});
|
|
}
|
|
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
const dragOffset = this.dragOffsets[i];
|
|
this.nodeDraggingScope.shadowElements[i].css('left', this.getXCoordinate(dragOffset.x + event.clientX) + 'px');
|
|
this.nodeDraggingScope.shadowElements[i].css('top', this.getYCoordinate(dragOffset.y + event.clientY) + 'px');
|
|
this.resizeCanvas(draggedNode, this.draggedElements[i]);
|
|
}
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
public dragend(event: Event | any) {
|
|
this.applyFunction(() => {
|
|
if (nodeDropScope.dropElement) {
|
|
nodeDropScope.dropElement.parentNode.removeChild(nodeDropScope.dropElement);
|
|
nodeDropScope.dropElement = null;
|
|
}
|
|
if (this.modelService.isDropSource()) {
|
|
return;
|
|
}
|
|
if (this.nodeDraggingScope.shadowElements.length) {
|
|
for (let i = 0; i < this.nodeDraggingScope.draggedNodes.length; i++) {
|
|
const draggedNode = this.nodeDraggingScope.draggedNodes[i];
|
|
const shadowElement = this.nodeDraggingScope.shadowElements[i];
|
|
draggedNode.x = parseInt(shadowElement.css('left').replace('px', ''), 10);
|
|
draggedNode.y = parseInt(shadowElement.css('top').replace('px', ''), 10);
|
|
this.modelService.canvasHtmlElement.removeChild(shadowElement[0]);
|
|
}
|
|
this.nodeDraggingScope.shadowElements.length = 0;
|
|
this.modelService.notifyModelChanged();
|
|
}
|
|
|
|
if (this.nodeDraggingScope.draggedNodes.length) {
|
|
this.nodeDraggingScope.draggedNodes.length = 0;
|
|
this.draggedElements.length = 0;
|
|
this.dragOffsets.length = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
export interface NodeDraggingScope {
|
|
draggedNodes: Array<FcNode>;
|
|
shadowElements: Array<JQuery<HTMLElement>>;
|
|
shadowDragStarted: boolean;
|
|
dropElement: HTMLElement;
|
|
}
|
|
|
|
export interface NodeDropElement extends HTMLElement {
|
|
offsetInfo?: {
|
|
offsetX: number;
|
|
offsetY: number;
|
|
};
|
|
}
|
|
|
|
export interface NodeDropScope {
|
|
dropElement: NodeDropElement;
|
|
}
|
|
|
|
export interface DropNodeInfo {
|
|
node: FcNode;
|
|
dropTargetId: string;
|
|
offsetX: number;
|
|
offsetY: number;
|
|
}
|