aboutsummaryrefslogtreecommitdiff
path: root/src/util/DragManager.ts
blob: e523408a3b45f5acdb951e6c4013d7373362311a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import {Opt} from "../fields/Field";
import {DocumentView} from "../views/nodes/DocumentView";
import {DocumentDecorations} from "../DocumentDecorations";
import {SelectionManager} from "./SelectionManager";

export namespace DragManager {
    export let rootId = "root";
    let dragDiv: HTMLDivElement;

    export enum DragButtons {
        Left = 1, Right = 2, Both = Left | Right
    }

    interface DragOptions {
        handlers: DragHandlers;

        hideSource: boolean | (() => boolean);
    }

    export interface DragDropDisposer {
        (): void;
    }

    export class DragCompleteEvent {
    }

    export interface DragHandlers {
        dragComplete: (e: DragCompleteEvent) => void;
    }

    export interface DropOptions {
        handlers: DropHandlers;
    }

    export class DropEvent {
        constructor(readonly x: number, readonly y: number, readonly data: {[ id: string ]: any}) {}
    }

    export interface DropHandlers {
        drop: (e: Event, de: DropEvent) => void;
    }

    export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
        if ("canDrop" in element.dataset) {
            throw new Error("Element is already droppable, can't make it droppable again");
        }
        element.dataset[ "canDrop" ] = "true";
        const handler = (e: Event) => {
            const ce = e as CustomEvent<DropEvent>;
            options.handlers.drop(e, ce.detail);
        };
        element.addEventListener("dashOnDrop", handler);
        return () => {
            element.removeEventListener("dashOnDrop", handler);
            delete element.dataset[ "canDrop" ]
        };
    }

    export function StartDrag(ele: HTMLElement, dragData: {[ id: string ]: any}, options: DragOptions) {
        if (!dragDiv) {
            const root = document.getElementById(rootId);
            if (!root) {
                throw new Error("No root element found");
            }
            dragDiv = document.createElement("div");
            root.appendChild(dragDiv);
        }
        const w = ele.offsetWidth, h = ele.offsetHeight;
        const rect = ele.getBoundingClientRect();
        const scaleX = rect.width / w, scaleY = rect.height / h;
        let x = rect.left, y = rect.top;
        // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
        let dragElement = ele.cloneNode(true) as HTMLElement;
        dragElement.style.opacity = "0.7";
        dragElement.style.position = "absolute";
        dragElement.style.transformOrigin = "0 0";
        dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
        dragDiv.appendChild(dragElement);

        let hideSource = false;
        if (typeof options.hideSource === "boolean") {
            hideSource = options.hideSource;
        } else {
            hideSource = options.hideSource();
        }
        const wasHidden = ele.hidden;
        if (hideSource) {
            ele.hidden = true;
        }

        const moveHandler = (e: PointerEvent) => {
            e.stopPropagation();
            e.preventDefault();
            x += e.movementX;
            y += e.movementY;
            dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
        };
        const upHandler = (e: PointerEvent) => {
            document.removeEventListener("pointermove", moveHandler, true);
            document.removeEventListener("pointerup", upHandler);
            FinishDrag(dragElement, e, options, dragData);
            if (hideSource && !wasHidden) {
                ele.hidden = false;
            }
        };
        document.addEventListener("pointermove", moveHandler, true);
        document.addEventListener("pointerup", upHandler);
    }

    function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: {[ index: string ]: any}) {
        dragDiv.removeChild(dragEle);
        const target = document.elementFromPoint(e.x, e.y);
        if (!target) {
            return;
        }
        target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
            bubbles: true,
            detail: {
                x: e.x,
                y: e.y,
                data: dragData
            }
        }));
        options.handlers.dragComplete({});
    }
}