aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/DragManager.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util/DragManager.ts')
-rw-r--r--src/client/util/DragManager.ts205
1 files changed, 94 insertions, 111 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a905dff0a..d1d7f2a8a 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,25 +1,17 @@
-import { Doc, Field, DocListCast } from "../../new_fields/Doc";
-import { Cast, ScriptCast, StrCast } from "../../new_fields/Types";
-import { emptyFunction } from "../../Utils";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { DocumentManager } from "./DocumentManager";
-import { LinkManager } from "./LinkManager";
-import { SelectionManager } from "./SelectionManager";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
-import { Docs, DocUtils } from "../documents/Documents";
-import { ScriptField } from "../../new_fields/ScriptField";
+import { action, observable, runInAction } from "mobx";
+import { DateField } from "../../new_fields/DateField";
+import { Doc, Field, Opt } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
import { PrefetchProxy } from "../../new_fields/Proxy";
import { listSpec } from "../../new_fields/Schema";
-import { Scripting } from "./Scripting";
-import { convertDropDataToButtons } from "./DropConverter";
-import { AudioBox } from "../views/nodes/AudioBox";
-import { DateField } from "../../new_fields/DateField";
-import { DocumentView } from "../views/nodes/DocumentView";
+import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
+import { ScriptField } from "../../new_fields/ScriptField";
+import { Cast, NumCast, ScriptCast, StrCast } from "../../new_fields/Types";
+import { emptyFunction } from "../../Utils";
+import { Docs, DocUtils } from "../documents/Documents";
+import * as globalCssVariables from "../views/globalCssVariables.scss";
import { UndoManager } from "./UndoManager";
-import { PointData } from "../../new_fields/InkField";
-import { MainView } from "../views/MainView";
+import { SnappingManager } from "./SnappingManager";
export type dropActionType = "alias" | "copy" | "move" | undefined; // undefined = move
export function SetupDrag(
@@ -55,10 +47,10 @@ export function SetupDrag(
const onItemDown = async (e: React.PointerEvent) => {
if (e.button === 0) {
e.stopPropagation();
- if (e.shiftKey && CollectionDockingView.Instance) {
+ if (e.shiftKey) {
e.persist();
const dragDoc = await docFunc();
- dragDoc && CollectionDockingView.Instance.StartOtherDrag({
+ dragDoc && DragManager.StartWindowDrag?.({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
@@ -75,8 +67,7 @@ export function SetupDrag(
export namespace DragManager {
let dragDiv: HTMLDivElement;
- export let horizSnapLines: number[];
- export let vertSnapLines: number[];
+ export let StartWindowDrag: Opt<((e: any, dragDocs: Doc[]) => void)> = undefined;
export function Root() {
const root = document.getElementById("root");
@@ -86,8 +77,8 @@ export namespace DragManager {
return root;
}
export let AbortDrag: () => void = emptyFunction;
- export type MoveFunction = (document: Doc, targetCollection: Doc | undefined, addDocument: (document: Doc) => boolean) => boolean;
- export type RemoveFunction = (document: Doc) => boolean;
+ export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
+ export type RemoveFunction = (document: Doc | Doc[]) => boolean;
export interface DragDropDisposer { (): void; }
export interface DragOptions {
@@ -184,7 +175,8 @@ export namespace DragManager {
export function MakeDropTarget(
element: HTMLElement,
dropFunc: (e: Event, de: DropEvent) => void,
- doc?: Doc
+ doc?: Doc,
+ preDropFunc?: (e: Event, de: DropEvent, targetAction: dropActionType) => void,
): DragDropDisposer {
if ("canDrop" in element.dataset) {
throw new Error(
@@ -195,9 +187,7 @@ export namespace DragManager {
const handler = (e: Event) => dropFunc(e, (e as CustomEvent<DropEvent>).detail);
const preDropHandler = (e: Event) => {
const de = (e as CustomEvent<DropEvent>).detail;
- if (de.complete.docDragData && doc?.targetDropAction) {
- de.complete.docDragData.dropAction = StrCast(doc.targetDropAction) as dropActionType;
- }
+ preDropFunc?.(e, de, StrCast(doc?.targetDropAction) as dropActionType);
};
element.addEventListener("dashOnDrop", handler);
doc && element.addEventListener("dashPreDrop", preDropHandler);
@@ -212,7 +202,7 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.creationDate && (dropDoc.creationDate = new DateField);
- dropDoc instanceof Doc && AudioBox.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: dropDoc }, { doc: d }, "audio link", "audio timeline"));
+ dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(dropDoc);
return dropDoc;
};
const batch = UndoManager.StartBatch("dragging");
@@ -243,40 +233,6 @@ export namespace DragManager {
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
}
- // drag links and drop link targets (aliasing them if needed)
- export async function StartLinkTargetsDrag(dragEle: HTMLElement, docView: DocumentView, downX: number, downY: number, sourceDoc: Doc, specificLinks?: Doc[]) {
- const draggedDocs = (specificLinks ? specificLinks : DocListCast(sourceDoc.links)).map(link => LinkManager.Instance.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[];
-
- if (draggedDocs.length) {
- const moddrag: Doc[] = [];
- for (const draggedDoc of draggedDocs) {
- const doc = await Cast(draggedDoc.annotationOn, Doc);
- if (doc) moddrag.push(doc);
- }
-
- const dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs);
- dragData.moveDocument = (doc: Doc, targetCollection: Doc | undefined, addDocument: (doc: Doc) => boolean): boolean => {
- docView.props.removeDocument?.(doc);
- addDocument(doc);
- return true;
- };
- const containingView = docView.props.ContainingCollectionView;
- const finishDrag = (e: DragCompleteEvent) =>
- e.docDragData && (e.docDragData.droppedDocuments =
- dragData.draggedDocuments.reduce((droppedDocs, d) => {
- const dvs = DocumentManager.Instance.getDocumentViews(d).filter(dv => dv.props.ContainingCollectionView === containingView);
- if (dvs.length) {
- dvs.forEach(dv => droppedDocs.push(dv.props.Document));
- } else {
- droppedDocs.push(Doc.MakeAlias(d));
- }
- return droppedDocs;
- }, [] as Doc[]));
-
- StartDrag([dragEle], dragData, downX, downY, undefined, finishDrag);
- }
- }
-
// drag&drop the pdf annotation anchor which will create a text note on drop via a dropCompleted() DragOption
export function StartPdfAnnoDrag(eles: HTMLElement[], dragData: PdfAnnoDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag(eles, dragData, downX, downY, options);
@@ -296,15 +252,64 @@ export namespace DragManager {
StartDrag([ele], {}, downX, downY);
}
- @action
export function SetSnapLines(horizLines: number[], vertLines: number[]) {
- horizSnapLines = horizLines;
- vertSnapLines = vertLines;
- MainView.Instance._hLines = horizLines;
- MainView.Instance._vLines = vertLines;
+ SnappingManager.setSnapLines(horizLines, vertLines);
}
+ export function snapDragAspect(dragPt: number[], snapAspect: number) {
+ let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ let near = dragPt;
+ const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
+ if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
+ const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
+ if (denominator === 0) return undefined; // Lines are parallel
+
+ let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
+ // let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
+ //if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
- function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
+ // Return a object with the x and y coordinates of the intersection
+ let x = x1 + ua * (x2 - x1)
+ let y = y1 + ua * (y2 - y1)
+ const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
+ return { pt: [x, y], dist }
+ }
+ SnappingManager.vertSnapLines().forEach((xCoord, i) => {
+ const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
+ if (pt && pt.dist < closest) {
+ closest = pt.dist;
+ near = pt.pt;
+ }
+ });
+ SnappingManager.horizSnapLines().forEach((yCoord, i) => {
+ const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
+ if (pt && pt.dist < closest) {
+ closest = pt.dist;
+ near = pt.pt;
+ }
+ });
+ return { thisX: near[0], thisY: near[1] };
+ }
+ // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
+ export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
+ const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10);
+ const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
+ if (snapLines.length) {
+ const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
+ const rangePts = [drag - offs[0], drag - offs[1], drag - offs[2]]; // left, mid, right or top, mid, bottom pts to try to snap to snaplines
+ const closestPts = rangePts.map(pt => snapLines.reduce((nearest, curr) => Math.abs(nearest - pt) > Math.abs(curr - pt) ? curr : nearest));
+ const closestDists = rangePts.map((pt, i) => Math.abs(pt - closestPts[i]));
+ const minIndex = closestDists[0] < closestDists[1] && closestDists[0] < closestDists[2] ? 0 : closestDists[1] < closestDists[2] ? 1 : 2;
+ return closestDists[minIndex] < snapThreshold ? closestPts[minIndex] + offs[minIndex] : drag;
+ }
+ return drag;
+ };
+ return {
+ thisX: snapVal([xFromLeft, xFromRight], e.pageX, SnappingManager.vertSnapLines()),
+ thisY: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.horizSnapLines())
+ };
+ }
+ export let docsBeingDragged: Doc[] = [];
+ export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) {
eles = eles.filter(e => e);
if (!dragDiv) {
dragDiv = document.createElement("div");
@@ -312,13 +317,13 @@ export namespace DragManager {
dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
- SelectionManager.SetIsDragging(true);
+ SnappingManager.SetIsDragging(true);
const scaleXs: number[] = [];
const scaleYs: number[] = [];
const xs: number[] = [];
const ys: number[] = [];
- const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
+ docsBeingDragged = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof PdfAnnoDragData ? [dragData.dragDocument] : [];
const elesCont = {
left: Number.MAX_SAFE_INTEGER,
top: Number.MAX_SAFE_INTEGER,
@@ -354,7 +359,7 @@ export namespace DragManager {
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
- if (docs.length) {
+ if (docsBeingDragged.length) {
const pdfBox = dragElement.getElementsByTagName("canvas");
const pdfBoxSrc = ele.getElementsByTagName("canvas");
Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
@@ -394,46 +399,22 @@ export namespace DragManager {
if (dragData instanceof DocumentDragData) {
dragData.userDropAction = e.ctrlKey && e.altKey ? "copy" : e.ctrlKey ? "alias" : undefined;
}
- if (e.shiftKey && CollectionDockingView.Instance && dragData.droppedDocuments.length === 1) {
+ if (e.shiftKey && dragData.droppedDocuments.length === 1) {
!dragData.dropAction && (dragData.dropAction = alias);
if (dragData.dropAction === "move") {
dragData.removeDocument?.(dragData.draggedDocuments[0]);
}
AbortDrag();
finishDrag?.(new DragCompleteEvent(true, dragData));
- CollectionDockingView.Instance.StartOtherDrag({
+ DragManager.StartWindowDrag?.({
pageX: e.pageX,
pageY: e.pageY,
preventDefault: emptyFunction,
button: 0
}, dragData.droppedDocuments);
}
- let thisX = e.pageX;
- let thisY = e.pageY;
- const currLeft = e.pageX - xFromLeft;
- const currTop = e.pageY - yFromTop;
- const currRight = e.pageX + xFromRight;
- const currBottom = e.pageY + yFromBottom;
- const closestLeft = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currLeft) > Math.abs(curr - currLeft) ? curr : prev);
- const closestTop = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currTop) > Math.abs(curr - currTop) ? curr : prev);
- const closestRight = vertSnapLines.reduce((prev, curr) => Math.abs(prev - currRight) > Math.abs(curr - currRight) ? curr : prev);
- const closestBottom = horizSnapLines.reduce((prev, curr) => Math.abs(prev - currBottom) > Math.abs(curr - currBottom) ? curr : prev);
- const distFromClosestLeft = Math.abs(e.pageX - xFromLeft - closestLeft);
- const distFromClosestTop = Math.abs(e.pageY - yFromTop - closestTop);
- const distFromClosestRight = Math.abs(e.pageX + xFromRight - closestRight);
- const distFromClosestBottom = Math.abs(e.pageY + yFromBottom - closestBottom);
- if (distFromClosestLeft < 10 && distFromClosestLeft < distFromClosestRight) {
- thisX = closestLeft + xFromLeft;
- }
- else if (distFromClosestRight < 10) {
- thisX = closestRight - xFromRight;
- }
- if (distFromClosestTop < 10 && distFromClosestTop < distFromClosestRight) {
- thisY = closestTop + yFromTop;
- }
- else if (distFromClosestBottom < 10) {
- thisY = closestBottom - yFromBottom;
- }
+
+ const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
alias = "move";
const moveX = thisX - lastX;
@@ -449,21 +430,22 @@ export namespace DragManager {
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
eles.map(ele => ele.parentElement && ele.parentElement?.className === dragData.dragDivName ? (ele.parentElement.hidden = false) : (ele.hidden = false));
};
- const endDrag = () => {
+ const endDrag = action(() => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- };
+ SnappingManager.clearSnapLines();
+ });
AbortDrag = () => {
hideDragShowOriginalElements();
- SelectionManager.SetIsDragging(false);
+ SnappingManager.SetIsDragging(false);
options?.dragComplete?.(new DragCompleteEvent(true, dragData));
endDrag();
};
const upHandler = (e: PointerEvent) => {
hideDragShowOriginalElements();
- dispatchDrag(eles, e, dragData, options, finishDrag);
- SelectionManager.SetIsDragging(false);
+ dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
+ SnappingManager.SetIsDragging(false);
endDrag();
options?.dragComplete?.(new DragCompleteEvent(false, dragData));
};
@@ -471,7 +453,8 @@ export namespace DragManager {
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any },
+ xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
const removed = dragData.dontHideOnDrop ? [] : dragEles.map(dragEle => {
const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow };
dragEle.style.width = "0";
@@ -485,14 +468,15 @@ export namespace DragManager {
r.ele.style.height = r.h;
r.ele.style.overflow = r.o;
});
+ const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
if (target) {
const complete = new DragCompleteEvent(false, dragData);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashPreDrop", {
bubbles: true,
detail: {
- x: e.x,
- y: e.y,
+ x: thisX,
+ y: thisY,
complete: complete,
shiftKey: e.shiftKey,
altKey: e.altKey,
@@ -506,8 +490,8 @@ export namespace DragManager {
new CustomEvent<DropEvent>("dashOnDrop", {
bubbles: true,
detail: {
- x: e.x,
- y: e.y,
+ x: thisX,
+ y: thisY,
complete: complete,
shiftKey: e.shiftKey,
altKey: e.altKey,
@@ -519,4 +503,3 @@ export namespace DragManager {
}
}
}
-Scripting.addGlobal(function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); });