diff options
Diffstat (limited to 'src/client/util/DragManager.ts')
-rw-r--r-- | src/client/util/DragManager.ts | 175 |
1 files changed, 94 insertions, 81 deletions
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9627c5df2..62f055f1a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,3 +1,5 @@ +/* eslint-disable import/no-mutable-exports */ +/* eslint-disable no-use-before-define */ /** * The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc) * @@ -13,32 +15,25 @@ */ import { action, observable, runInAction } from 'mobx'; +import { ClientUtils } from '../../ClientUtils'; +import { emptyFunction } from '../../Utils'; import { DateField } from '../../fields/DateField'; -import { Doc, Field, Opt, StrListCast } from '../../fields/Doc'; +import { Doc, FieldType, Opt, StrListCast } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; import { ScriptCast } from '../../fields/Types'; -import { emptyFunction, Utils } from '../../Utils'; -import { Docs, DocUtils } from '../documents/Documents'; +import { DocUtils, Docs } from '../documents/Documents'; import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../views/nodes/DocumentView'; +import { dropActionType } from './DropActionTypes'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; -import { DocData } from '../../fields/DocSymbols'; -const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore -export enum dropActionType { - embed = 'embed', // create a new embedding of the dragged document for the new location - copy = 'copy', // copy the dragged document - move = 'move', // move the dragged document to the drop location after removing it from where it was - add = 'add', // add the dragged document to the drop location without removing it from where it was - same = 'same', // only allow drop within same collection (or same hierarchical tree collection) - inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier) - proto = 'proto', -} // undefined = move, same = move but doesn't call dropPropertiesToRemove +const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore /** * Initialize drag @@ -83,6 +78,9 @@ export namespace DragManager { let dragLabel: HTMLDivElement; export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>; export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; + export let AbortDrag: () => void = emptyFunction; + export const docsBeingDragged: Doc[] = observable([]); + export let DocDragData: DocumentDragData | undefined; export function Root() { const root = document.getElementById('root'); @@ -91,7 +89,6 @@ export namespace DragManager { } return root; } - export let AbortDrag: () => void = emptyFunction; export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; export type RemoveFunction = (document: Doc | Doc[]) => boolean; @@ -106,6 +103,7 @@ export namespace DragManager { // event called when the drag operation results in a drop action export class DropEvent { + // eslint-disable-next-line no-useless-constructor constructor( readonly x: number, readonly y: number, @@ -115,7 +113,9 @@ export namespace DragManager { readonly metaKey: boolean, readonly ctrlKey: boolean, readonly embedKey: boolean - ) {} + ) { + /* empty */ + } } // event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated @@ -194,7 +194,7 @@ export namespace DragManager { userDropAction?: dropActionType; } - let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { if (de.complete.docDragData) { targetAction && (de.complete.docDragData.dropAction = targetAction); e.stopPropagation(); @@ -228,7 +228,7 @@ export namespace DragManager { return dropDoc; }; const finishDrag = async (e: DragCompleteEvent) => { - const docDragData = e.docDragData; + const { docDragData } = e; setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.())); onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { @@ -256,7 +256,9 @@ export namespace DragManager { .forEach((drop: Doc, i: number) => { const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove); const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps)); - [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined)); + [...remProps, 'dropPropertiesToRemove'].forEach(prop => { + drop[prop] = undefined; + }); }); } return e; @@ -268,15 +270,18 @@ export namespace DragManager { } // drag a button template and drop a new button - export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { + export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) { const finishDrag = (e: DragCompleteEvent) => { const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) }); - params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields + params.forEach(p => { + Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc)); + }); // copy all "captured" arguments into document parameterfields initialize?.(bd); bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params); e.docDragData && (e.docDragData.droppedDocuments = [bd]); return e; }; + // eslint-disable-next-line no-param-reassign options = options ?? {}; options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag); @@ -298,7 +303,7 @@ export namespace DragManager { } export function snapDragAspect(dragPt: number[], snapAspect: number) { - let closest = Utils.SNAP_THRESHOLD; + let closest = ClientUtils.SNAP_THRESHOLD; 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 @@ -307,7 +312,7 @@ export namespace DragManager { const 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 + // if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments // Return a object with the x and y coordinates of the intersection const x = x1 + ua * (x2 - x1); @@ -315,14 +320,14 @@ export namespace DragManager { const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y)); return { pt: [x, y], dist }; }; - SnappingManager.VertSnapLines.forEach((xCoord, i) => { + SnappingManager.VertSnapLines.forEach(xCoord => { 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) => { + SnappingManager.HorizSnapLines.forEach(yCoord => { 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; @@ -333,7 +338,7 @@ export namespace DragManager { } // 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 = Utils.SNAP_THRESHOLD; + const snapThreshold = ClientUtils.SNAP_THRESHOLD; 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 @@ -350,14 +355,33 @@ export namespace DragManager { y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines), }; } - export let docsBeingDragged: Doc[] = observable([]); - export let CanEmbed = false; - export let DocDragData: DocumentDragData | undefined; - export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) { + + async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { + const dropArgs = { + cancelable: true, // allows preventDefault() to be called to cancel the drop + bubbles: true, + detail: { + ...pos, + complete, + shiftKey: e.shiftKey, + altKey: e.altKey, + metaKey: e.metaKey, + ctrlKey: e.ctrlKey, + embedKey: SnappingManager.CanEmbed, + }, + }; + target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs)); + UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes) + await finishDrag?.(complete); + UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called + options?.dragComplete?.(complete); + endDrag?.(); + } + export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) { if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return; DocDragData = dragData as DocumentDragData; const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag'); - eles = eles.filter(e => e); + const eles = elesIn.filter(e => e); SnappingManager.SetCanEmbed(dragData.canEmbed || false); if (!dragDiv) { dragDiv = document.createElement('div'); @@ -375,9 +399,9 @@ export namespace DragManager { } Object.assign(dragDiv.style, { width: '', height: '', overflow: '' }); dragDiv.hidden = false; - const scalings: number[] = [], - xs: number[] = [], - ys: number[] = []; + const scalings: number[] = []; + const xs: number[] = []; + const ys: number[] = []; const elesCont = { left: Number.MAX_SAFE_INTEGER, @@ -385,7 +409,7 @@ export namespace DragManager { top: Number.MAX_SAFE_INTEGER, bottom: Number.MIN_SAFE_INTEGER, }; - let rot: number[] = []; + const rot: number[] = []; const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : []; const dragElements = eles.map(ele => { // bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element @@ -471,7 +495,9 @@ export namespace DragManager { .filter(pb => pb.width && pb.height) .map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } - [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none')); + [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => { + (ele as any).style && ((ele as any).style.pointerEvents = 'none'); + }); dragDiv.appendChild(dragElement); if (dragElement !== ele) { @@ -493,7 +519,11 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none'; !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - setTimeout(() => eles.forEach(ele => (ele.hidden = hide))); + setTimeout(() => + eles.forEach(ele => { + ele.hidden = hide; + }) + ); }; options?.hideSource && hideDragShowOriginalElements(true); @@ -505,22 +535,7 @@ export namespace DragManager { const yFromBottom = elesCont.bottom - downY; let scrollAwaiter: Opt<NodeJS.Timeout>; - AbortDrag = () => { - options?.dragComplete?.(new DragCompleteEvent(true, dragData)); - cleanupDrag(true); - }; - - const cleanupDrag = action((undo: boolean) => { - (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.()); - hideDragShowOriginalElements(false); - document.removeEventListener('pointermove', moveHandler, true); - document.removeEventListener('pointerup', upHandler, true); - SnappingManager.SetIsDragging(false); - if (batch.end() && undo) UndoManager.Undo(); - docsBeingDragged.length = 0; - SnappingManager.SetCanEmbed(false); - }); - var startWindowDragTimer: any; + let startWindowDragTimer: any; const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { @@ -580,10 +595,10 @@ export namespace DragManager { defaultPrevented: true, eventPhase: e.eventPhase, isTrusted: true, - preventDefault: () => ('not implemented for this event' ? false : false), - isDefaultPrevented: () => ('not implemented for this event' ? false : false), - stopPropagation: () => ('not implemented for this event' ? false : false), - isPropagationStopped: () => ('not implemented for this event' ? false : false), + preventDefault: () => 'not implemented for this event' && false, + isDefaultPrevented: () => 'not implemented for this event' && false, + stopPropagation: () => 'not implemented for this event' && false, + isPropagationStopped: () => 'not implemented for this event' && false, persist: emptyFunction, timeStamp: e.timeStamp, type: 'dashDragMovePause', @@ -602,7 +617,9 @@ export namespace DragManager { const moveVec = { x: x - lastPt.x, y: y - lastPt.y }; lastPt = { x, y }; - dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`)); + dragElements.forEach((dragElement, i) => { + dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`; + }); dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`; }; const upHandler = (e: PointerEvent) => { @@ -610,36 +627,32 @@ export namespace DragManager { startWindowDragTimer = undefined; dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false)); }; + const cleanupDrag = action((undo: boolean) => { + (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.()); + hideDragShowOriginalElements(false); + document.removeEventListener('pointermove', moveHandler, true); + document.removeEventListener('pointerup', upHandler, true); + SnappingManager.SetIsDragging(false); + if (batch.end() && undo) UndoManager.Undo(); + docsBeingDragged.length = 0; + SnappingManager.SetCanEmbed(false); + }); + AbortDrag = () => { + options?.dragComplete?.(new DragCompleteEvent(true, dragData)); + cleanupDrag(true); + }; document.addEventListener('pointermove', moveHandler, true); document.addEventListener('pointerup', upHandler, true); } - - async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) { - const dropArgs = { - cancelable: true, // allows preventDefault() to be called to cancel the drop - bubbles: true, - detail: { - ...pos, - complete, - shiftKey: e.shiftKey, - altKey: e.altKey, - metaKey: e.metaKey, - ctrlKey: e.ctrlKey, - embedKey: SnappingManager.CanEmbed, - }, - }; - target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs)); - UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes) - await finishDrag?.(complete); - UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called - options?.dragComplete?.(complete); - endDrag?.(); - } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { if (readOnly) { return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged); } - SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged)); + SelectionManager.Views.forEach(dv => { + dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged; + }); + return undefined; }); |