diff options
| author | bobzel <zzzman@gmail.com> | 2021-01-11 11:33:46 -0500 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2021-01-11 11:33:46 -0500 |
| commit | 6c1857193aa51ccfec081aaa4372de164e152355 (patch) | |
| tree | 8ae584d0c0a262df63153fec097f32ab9ef80441 /src/client/views/collections/collectionFreeForm | |
| parent | 768ab508529ab2b7f43a44592e5dc2c9f15a68a5 (diff) | |
| parent | e80e0e3938f1f7c5f740553eb5cb7b152f2598e8 (diff) | |
Merge branch 'master' into ink_edits
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
7 files changed, 372 insertions, 536 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index 8cbda310a..858719a08 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,7 +1,6 @@ .collectionfreeformlinkview-linkLine { stroke: black; opacity: 0.8; - pointer-events: all; stroke-width: 3px; transition: opacity 0.5s ease-in; fill: transparent; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 4cf257640..ae5688b48 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,14 +1,14 @@ +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../../fields/Doc"; -import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../../Utils'; +import { Id } from "../../../../fields/FieldSymbols"; +import { NumCast, StrCast } from "../../../../fields/Types"; +import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { SnappingManager } from "../../../util/SnappingManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); -import { DocumentType } from "../../../documents/DocumentTypes"; -import { observable, action, reaction, IReactionDisposer, trace, computed } from "mobx"; -import { StrCast, Cast, NumCast } from "../../../../fields/Types"; -import { Id } from "../../../../fields/FieldSymbols"; -import { SnappingManager } from "../../../util/SnappingManager"; export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -40,6 +40,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return; setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. + if (!linkDoc.linkAutoMove) return; const acont = A.props.Document.type === DocumentType.LINK ? A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; const bcont = B.props.Document.type === DocumentType.LINK ? B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : []; const adiv = (acont.length ? acont[0] : A.ContentDiv); @@ -60,6 +61,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const linkEles = Array.from(window.document.getElementsByClassName(linkDoc[Id])); const targetAhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[afield] as Doc)[Id])); const targetBhyperlink = linkEles.find((ele: any) => ele.dataset.targetids?.includes((linkDoc[bfield] as Doc)[Id])); + if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return; if (!targetBhyperlink) { A.rootDoc[afield + "_x"] = (apt.point.x - aleft) / awidth * 100; A.rootDoc[afield + "_y"] = (apt.point.y - atop) / aheight * 100; @@ -101,8 +103,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const top = rect.top, height = rect.height; var el = el.parentNode; while (el && el !== document.body) { - if (el.hasOwnProperty("getBoundingClientRect")) { - rect = el.getBoundingClientRect(); + rect = el.getBoundingClientRect?.(); + if (rect?.width) { if (top <= rect.bottom === false && getComputedStyle(el).overflow === "hidden") return rect.bottom; // Check if the element is out of view due to a container scrolling if ((top + height) <= rect.top && getComputedStyle(el).overflow === "hidden") return rect.top; @@ -117,8 +119,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const left = rect.left, width = rect.width; var el = el.parentNode; while (el && el !== document.body) { - if (el.hasOwnProperty("getBoundingClientRect")) { - rect = el.getBoundingClientRect(); + rect = el?.getBoundingClientRect(); + if (rect?.width) { if (left <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.right; // Check if the element is out of view due to a container scrolling if ((left + width) <= rect.left && getComputedStyle(el).overflow === "hidden") return rect.left; @@ -138,11 +140,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const adiv = (acont.length ? acont[0] : A.ContentDiv); const bdiv = (bcont.length ? bcont[0] : B.ContentDiv); for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; - for (let apdiv = bdiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; + for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return; const a = adiv.getBoundingClientRect(); const b = bdiv.getBoundingClientRect(); const atop = this.visibleY(adiv); const btop = this.visibleY(bdiv); + if (!a.width || !b.width) return undefined; + const atop2 = this.visibleY(adiv); + const btop2 = this.visibleY(bdiv); const aleft = this.visibleX(adiv); const bleft = this.visibleX(bdiv); const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 6a1a41ca7..4dab8f15b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,15 +1,14 @@ -import { computed, trace } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; +import { Utils } from "../../../../Utils"; +import { DocumentType } from "../../../documents/DocumentTypes"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); -import { Utils, emptyFunction } from "../../../../Utils"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { SnappingManager } from "../../../util/SnappingManager"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -28,10 +27,8 @@ export class CollectionFreeFormLinksView extends React.Component { } return drawnPairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); - return connections.filter(c => - c.a.props.Document.type === DocumentType.LINK - && !c.a.props.treeViewDoc?.treeViewHideLinkLines && !c.b.props.treeViewDoc?.treeViewHideLinkLines - ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); + return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) + .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); } render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 548ad78a5..9f6938e67 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,16 +1,16 @@ +import { computed } from "mobx"; import { observer } from "mobx-react"; import * as mobxUtils from 'mobx-utils'; import CursorField from "../../../../fields/CursorField"; +import { FieldResult } from "../../../../fields/Doc"; +import { List } from "../../../../fields/List"; import { listSpec } from "../../../../fields/Schema"; import { Cast } from "../../../../fields/Types"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { CollectionViewProps } from "../CollectionSubView"; +import { CollectionViewProps } from "../CollectionView"; import "./CollectionFreeFormView.scss"; import React = require("react"); import v5 = require("uuid/v5"); -import { computed } from "mobx"; -import { FieldResult } from "../../../../fields/Doc"; -import { List } from "../../../../fields/List"; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index a50b41198..a05c25c9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -17,6 +17,7 @@ position: absolute; top: 0; left: 0; + pointer-events: none; } .collectionfreeformview-viewdef { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1033050b9..f934fcd92 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; @@ -12,7 +12,7 @@ import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal, returnTrue } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -31,9 +31,9 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { Timeline } from "../../animationtimeline/Timeline"; import { ContextMenu } from "../../ContextMenu"; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentLinksButton } from "../../nodes/DocumentLinksButton"; -import { DocumentViewProps, DocAfterFocusFunc } from "../../nodes/DocumentView"; +import { DocumentViewProps, DocAfterFocusFunc, DocumentView } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { PresBox } from "../../nodes/PresBox"; @@ -47,6 +47,9 @@ import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; +import { StyleProp, StyleLayers } from "../../StyleProvider"; +import { DocumentDecorations } from "../../DocumentDecorations"; +import { FieldViewProps } from "../../nodes/FieldView"; export const panZoomSchema = createSchema({ _panX: "number", @@ -73,12 +76,15 @@ export type collectionFreeformViewProps = { forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; childPointerEvents?: boolean; + parentActive: (outsideReaction: boolean) => boolean; scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) }; @observer export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) { + public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive + private _lastX: number = 0; private _lastY: number = 0; private _downX: number = 0; @@ -87,30 +93,35 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private _inkToTextStartY: number | undefined; private _wordPalette: Map<string, string> = new Map<string, string>(); private _clusterDistance: number = 75; - private _hitCluster = false; + private _hitCluster: number = -1; private _layoutComputeReaction: IReactionDisposer | undefined; private _boundsReaction: IReactionDisposer | undefined; private _layoutPoolData = new ObservableMap<string, PoolData>(); private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>(); private _cachedPool: Map<string, PoolData> = new Map(); + private _lastTap = 0; + private _nudgeTime = 0; + private _thumbIdentifier?: number; + + @observable private _hLines: number[] | undefined; + @observable private _vLines: number[] | undefined; @observable private _pullCoords: number[] = [0, 0]; @observable private _pullDirection: string = ""; + @observable private _showAnimTimeline = false; + @observable ChildDrag: DocumentView | undefined; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @observable _clusterSets: (Doc[])[] = []; @observable _timelineRef = React.createRef<Timeline>(); - @observable _marqueeRef = React.createRef<HTMLDivElement>(); - @observable canPanX: boolean = true; - @observable canPanY: boolean = true; + @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); } @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; } - @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; } - @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent ? this.props.ContentScaling() : 1; } + @computed get fitToContent() { return (this.props.fitContentsToDoc || this.Document._fitToBox) && !this.isAnnotationOverlay; } + @computed get parentScaling() { return 1; } @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } - @computed get nativeWidth() { return this.fitToContent ? 0 : returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.Document)); } - @computed get nativeHeight() { return this.fitToContent ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.Document)); } + @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } + @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); } private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } @@ -153,39 +164,26 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newBox); } + addDocument = action((newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { - retVal = this.props.addDocument(newBox); + retVal = this.props.addDocument?.(newBox) || false; retVal && this.bringToFront(newBox); retVal && this.updateCluster(newBox); } else { - retVal = this.props.addDocument(newBox); + retVal = this.props.addDocument?.(newBox) || false; // bcz: deal with clusters } if (retVal) { const newBoxes = (newBox instanceof Doc) ? [newBox] : newBox; for (const newBox of newBoxes) { if (newBox.activeFrame !== undefined) { - const x = newBox.x; - const y = newBox.y; - const w = newBox._width; - const h = newBox._height; - delete newBox["x-indexed"]; - delete newBox["y-indexed"]; - delete newBox["w-indexed"]; - delete newBox["h-indexed"]; - delete newBox["opacity-indexed"]; - delete newBox._width; - delete newBox._height; - delete newBox.x; - delete newBox.y; - delete newBox.opacity; + const vals = CollectionFreeFormDocumentView.animFields.map(field => newBox[field]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]); + CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]); delete newBox.activeFrame; - newBox.x = x; - newBox.y = y; - newBox._width = w; - newBox._height = h; + CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== "opacity" && (newBox[field] = vals[i])); } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { @@ -197,7 +195,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); + docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); } public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document._currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } @@ -209,8 +207,28 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); } + updateGroupBounds = () => { + if (!this.props.Document._isGroup) return; + const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); + const cbounds = aggregateBounds(clist, 0, 0); + const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; + const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; + const pbounds = { + x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], + r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1] + }; + + this.layoutDoc._width = (pbounds.r - pbounds.x); + this.layoutDoc._height = (pbounds.b - pbounds.y); + this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; + this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; + this.layoutDoc.x = pbounds.x; + this.layoutDoc.y = pbounds.y; + } + @action internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) { + if (!this.ChildDrag && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false; if (!super.onInternalDrop(e, de)) return false; const refDoc = docDragData.droppedDocuments[0]; const [xpo, ypo] = this.getTransformOverlay().transformPoint(de.x, de.y); @@ -220,13 +238,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); - const dropPos = this.Document._currentFrame !== undefined ? [dvals.x, dvals.y] : [NumCast(refDoc.x), NumCast(refDoc.y)]; + const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)]; for (let i = 0; i < docDragData.droppedDocuments.length; i++) { const d = docDragData.droppedDocuments[i]; const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { + CollectionFreeFormDocumentView.setupKeyframes([d], this.Document._currentFrame, false); const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, this.Document.editScrollProgressivize ? vals.scroll : undefined, vals.opacity); + vals.x = x + (vals.x || 0) - dropPos[0]; + vals.y = y + (vals.y || 0) - dropPos[1]; + vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined; + CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, vals); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; @@ -234,9 +256,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)]; layoutDoc._width = NumCast(layoutDoc._width, 300); layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? nd[1] / nd[0] * NumCast(layoutDoc._width) : 300); - !d._isBackground && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront + !StrListCast(d.layers).includes(StyleLayers.Background) && (d._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront } + this.updateGroupBounds(); + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); return true; } @@ -264,8 +288,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return false; } else { const source = Docs.Create.TextDocument("", { _width: 200, _height: 75, x: xp, y: yp, title: "dropped annotation" }); - this.props.addDocument(source); - linkDragData.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed + this.props.addDocument?.(source); + de.complete.linkDocument = DocUtils.MakeLink({ doc: source }, { doc: linkDragData.linkSourceDocument }, "doc annotation", ""); // TODODO this is where in text links get passed e.stopPropagation(); return true; } @@ -273,39 +297,37 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - // if (this.props.Document._isBackground) return false; const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); - if (this.isAnnotationOverlay !== true && de.complete.linkDragData) { - return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); - } else if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de)) { - return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); - } else if (de.complete.docDragData?.droppedDocuments.length && this.internalDocDrop(e, de, de.complete.docDragData, xp, yp)) { - return true; - } + if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); + if (de.complete.annoDragData?.dropDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - const layoutDoc = Doc.Layout(cd); - const cx = NumCast(cd.x) - this._clusterDistance; - const cy = NumCast(cd.y) - this._clusterDistance; - const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; - const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; - return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? - NumCast(cd.cluster) : cluster; + const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); + if (grouping !== -1) { + const layoutDoc = Doc.Layout(cd); + const cx = NumCast(cd.x) - this._clusterDistance; + const cy = NumCast(cd.y) - this._clusterDistance; + const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; + const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; + } + return cluster; }, -1); } - tryDragCluster(e: PointerEvent | TouchEvent) { - const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0); - if (ptsParent) { - const cluster = this.pickCluster(this.getTransform().transformPoint(ptsParent.clientX, ptsParent.clientY)); - if (cluster !== -1) { - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); + + tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) { + if (cluster !== -1) { + const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0); + if (ptsParent) { + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster); const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); const de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; - const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); + const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction }); @@ -363,10 +385,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P updateCluster(doc: Doc) { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.props.Document._useClusters) { - this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); + this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); const preferredInd = NumCast(doc.cluster); doc.cluster = -1; - this._clusterSets.map((set, i) => set.map(member => { + this._clusterSets.forEach((set, i) => set.forEach(member => { if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { doc.cluster = i; } @@ -374,7 +396,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } - this._clusterSets.map((set, i) => { + this._clusterSets.forEach((set, i) => { if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { doc.cluster = i; } @@ -389,40 +411,33 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - getClusterColor = (doc: Opt<Doc>) => { - let clusterColor = this.props.backgroundColor?.(doc, this.props.renderDepth + 1); + getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { + let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 + if (property !== StyleProp.BackgroundColor) return styleProp; const cluster = NumCast(doc?.cluster); if (this.Document._useClusters) { if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc), 0); + setTimeout(() => doc && this.updateCluster(doc)); } else { // choose a cluster color from a palette const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"]; - clusterColor = colors[cluster % colors.length]; + styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set && set.filter(s => !s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); - set && set.filter(s => s._isBackground).map(s => clusterColor = StrCast(s.backgroundColor)); + set && set.filter(s => !StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set && set.filter(s => StrListCast(s.layers).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); } - } - return clusterColor; + } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; + return styleProp; } - @action onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) { return; } - this._hitCluster = this.props.Document._useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; - if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) { - - // if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) { - // this.props.addDocTab(this.props.Document, "replace"); - // e.stopPropagation(); - // e.preventDefault(); - // return; - // } + this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); + if (e.button === 0 && (!e.shiftKey || this._hitCluster !== -1) && !e.altKey && !e.ctrlKey && this.props.active(true)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -446,18 +461,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true); const pt = me.changedTouches[0]; if (pt) { - this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; + this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)); if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); this.addEndListeners(); - // if (Doc.SelectedTool() === InkTool.Highlighter || Doc.SelectedTool() === InkTool.Pen) { - // e.stopPropagation(); - // e.preventDefault(); - // const point = this.getTransform().transformPoint(pt.pageX, pt.pageY); - // this._points.push({ X: point[0], Y: point[1] }); - // } if (Doc.GetSelectedTool() === InkTool.None) { this._lastX = pt.pageX; this._lastY = pt.pageY; @@ -502,7 +511,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return pass; }); this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument(d)); + sel.forEach(d => this.props.removeDocument?.(d)); e.stopPropagation(); break; case GestureUtils.Gestures.StartBracket: @@ -520,12 +529,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (sets.length && sets[0]) { this._wordPalette.clear(); const colors = setDocs.map(sd => FieldValue(sd.color) as string); - sets.forEach((st: string, i: number) => { - const words = st.split(","); - words.forEach(word => { - this._wordPalette.set(word, colors[i]); - }); - }); + sets.forEach((st: string, i: number) => st.split(",").forEach(word => this._wordPalette.set(word, colors[i]))); } const inks = this.getActiveDocuments().filter(doc => { if (doc.type === "ink") { @@ -588,27 +592,20 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - _lastTap = 0; - - @action onPointerUp = (e: PointerEvent): void => { - if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return; - - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - this.removeMoveListeners(); - this.removeEndListeners(); + if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + this.removeMoveListeners(); + this.removeEndListeners(); + } } onClick = (e: React.MouseEvent) => { if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { - if (Date.now() - this._lastTap < 300) { - runInAction(() => { - DocumentLinksButton.StartLink = undefined; - DocumentLinksButton.StartLinkView = undefined; - }); - const docpt = this.getTransform().transformPoint(e.clientX, e.clientY); - this.scaleAtPt(docpt, 1); + if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click + runInAction(() => DocumentLinksButton.StartLink = DocumentLinksButton.StartLinkView = undefined); + this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); e.stopPropagation(); e.preventDefault(); } @@ -618,9 +615,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { - // bcz: theres should be a better way of doing these than referencing these static instances directly - MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2 - const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true); this._lastX = e.clientX; @@ -639,8 +633,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return; } if (!e.cancelBubble) { + if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag if (Doc.GetSelectedTool() === InkTool.None) { - if (this._hitCluster && this.tryDragCluster(e)) { + if (this.tryDragCluster(e, this._hitCluster)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); document.removeEventListener("pointermove", this.onPointerMove); @@ -660,7 +655,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const pt = myTouches[0]; if (pt) { if (Doc.GetSelectedTool() === InkTool.None) { - if (this._hitCluster && this.tryDragCluster(e)) { + if (this.tryDragCluster(e, this._hitCluster)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); document.removeEventListener("pointermove", this.onPointerMove); @@ -761,7 +756,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -789,7 +783,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.removeEndListeners(); } - @action zoom = (pointX: number, pointY: number, deltaY: number): void => { let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05; @@ -860,7 +853,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } bringToFront = action((doc: Doc, sendToBack?: boolean) => { - if (sendToBack || doc._isBackground) { + if (sendToBack || StrListCast(doc.layers).includes(StyleLayers.Background)) { doc.zIndex = 0; } else if (doc.isInkMask) { doc.zIndex = 5000; @@ -911,7 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P !dontCenter && this.props.focus(doc); afterFocus && setTimeout(afterFocus, delay); } else { - const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height); + const contextHgt = NumCast(annotOn._height); const curScroll = NumCast(this.props.Document._scrollTop); let scrollTo = curScroll; if (curScroll + contextHgt < NumCast(doc.y)) { @@ -927,7 +920,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { !dontCenter && delay && this.props.focus(this.props.Document); afterFocus?.(!dontCenter && delay ? true : false); - } } @@ -942,7 +934,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); - if (DocListCast(this.dataDoc[this.props.annotationsKey || this.props.fieldKey]).includes(doc)) { + if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... if (!doc.z) this.setPan(newPanX, newPanY, doc.focusSpeed || doc.focusSpeed === 0 ? `transform ${doc.focusSpeed}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } @@ -974,12 +966,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P pw && ph && (this.Document[this.scaleFieldKey] = scale * Math.min(pw / NumCast(doc._width), ph / NumCast(doc._height))); } - @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } - @computed get backgroundActive() { return this.layoutDoc._isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); } onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); - backgroundHalo = () => BoolCast(this.Document._useClusters); - parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; + parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { addDocument: this.props.addDocument, @@ -987,35 +976,30 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P moveDocument: this.props.moveDocument, pinToPres: this.props.pinToPres, whenActiveChanged: this.props.whenActiveChanged, - fitToBox: false, + parentActive: this.parentActive, DataDoc: childData, Document: childLayout, - LibraryPath: this.libraryPath, - LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate, - LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString, - FreezeDimensions: this.props.freezeChildDimensions, - setupDragLines: this.setupDragLines, - dontRegisterView: this.props.dontRegisterView, + ContainingCollectionView: this.props.CollectionView, + ContainingCollectionDoc: this.props.Document, + LayoutTemplate: childLayout.z ? undefined : this.props.childLayoutTemplate, + LayoutTemplateString: childLayout.z ? undefined : this.props.childLayoutString, rootSelected: childData ? this.rootSelected : returnFalse, - dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, onClick: this.onChildClickHandler, onDoubleClick: this.onChildDoubleClickHandler, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, - renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], PanelHeight: childLayout[HeightSym], - ContentScaling: returnOne, - ContainingCollectionView: this.props.CollectionView, - ContainingCollectionDoc: this.props.Document, docFilters: this.docFilters, docRangeFilters: this.docRangeFilters, searchFilterDocs: this.searchFilterDocs, focus: this.focusDocument, - backgroundColor: this.getClusterColor, - backgroundHalo: this.backgroundHalo, - parentActive: this.parentActive, + styleProvider: this.getClusterColor, + freezeDimensions: this.props.childFreezeDimensions, + dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, bringToFront: this.bringToFront, addDocTab: this.addDocTab, + renderDepth: this.props.renderDepth + 1, + dontRegisterView: this.props.dontRegisterView, }; } @@ -1025,14 +1009,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = pt[0]; doc.y = pt[1]; - return this.props.addDocument(doc); + return this.props.addDocument?.(doc) || false; } else { (doc as any as Doc[]).forEach(doc => { const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = pt[0]; doc.y = pt[1]; }); - return this.props.addDocument(doc); + return this.props.addDocument?.(doc) || false; } } if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { @@ -1041,9 +1025,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } return this.props.addDocTab(doc, where); }); + getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { const layoutDoc = Doc.Layout(params.pair.layout); - const { x, y, opacity } = this.Document._currentFrame === undefined ? params.pair.layout : + const { x, y, opacity } = this.Document._currentFrame === undefined ? + { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } : CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame || 0); const { z, color, zIndex } = params.pair.layout; return { @@ -1090,6 +1076,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { return this._layoutPoolData.get(doc[Id] + (replica || "")); }.bind(this)); + childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) { return this._layoutSizeData.get(doc[Id] + (replica || "")); }.bind(this)); @@ -1121,7 +1108,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @computed get doInternalLayoutComputation() { TraceMobx(); - const newPool = new Map<string, PoolData>(); const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); switch (engine) { @@ -1158,15 +1144,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P key={entry[1].pair.layout[Id] + (entry[1].replica || "")} {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)} replica={entry[1].replica} + CollectionFreeFormView={this} dataProvider={this.childDataProvider} sizeProvider={this.childSizeProvider} + layerProvider={this.props.layerProvider} pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" : (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined} jitterRotation={NumCast(this.props.Document._jitterRotation) || ((Doc.UserDoc().renderStyle === "comic" ? 10 : 0))} //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this - fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this - FreezeDimensions={BoolCast(this.props.freezeChildDimensions)} + freezeDimensions={BoolCast(this.props.childFreezeDimensions)} />, bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); @@ -1186,7 +1173,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this._layoutComputeReaction = reaction(() => this.doLayoutComputation, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); - if (!this.props.annotationsKey) { + if (!this.props.isAnnotationOverlay) { this._boundsReaction = reaction(() => this.contentBounds, bounds => (!this.fitToContent && this._layoutElements?.length) && setTimeout(() => { const rbounds = Cast(this.Document._renderContentBounds, listSpec("number"), [0, 0, 0, 0]); @@ -1213,9 +1200,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - - // <div ref={this._marqueeRef}> - @action onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => { if ((e as any).handlePan || this.props.isAnnotationOverlay) return; @@ -1247,7 +1231,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.ContainingCollectionView?.removeDocument(this.props.Document); })); - @undoBatch layoutDocsInGrid = action(() => { const docs = this.childLayoutPairs; @@ -1272,7 +1255,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @undoBatch @action toggleNativeDimensions = () => { - Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth?.() || 0, this.props.NativeHeight?.() || 0); + Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); } @undoBatch @@ -1281,23 +1264,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.layoutDoc._lockedTransform = this.layoutDoc._lockedTransform ? undefined : true; } - private thumbIdentifier?: number; - onContextMenu = (e: React.MouseEvent) => { - if (this.props.annotationsKey || this.props.Document.annotationOn || !ContextMenu.Instance) return; + if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return; const appearance = ContextMenu.Instance.findByDescription("Appearance..."); const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + this.props.ContainingCollectionView && + appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); @@ -1305,15 +1287,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode && - optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: "eye" }); - this.props.ContainingCollectionView && - optionItems.push({ description: "Move Items Out of Collection", event: this.promoteCollection, icon: "table" }); + optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" }); optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" }); this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); - } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); const mores = ContextMenu.Instance.findByDescription("More..."); @@ -1342,11 +1321,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); doc.x = xx, doc.y = yy; this.props.addDocument?.(doc); - setTimeout(() => { + setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => { docs.docs.forEach(d => LinkManager.Instance.addLink(d)); - }); - }, 2000); // need to give solr some time to update so that this query will find any link docs we've added. + }), 2000); // need to give solr some time to update so that this query will find any link docs we've added. } } } @@ -1354,57 +1332,38 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P input.click(); } - - @observable showTimeline = false; - - intersectRect(r1: { left: number, top: number, width: number, height: number }, - r2: { left: number, top: number, width: number, height: number }) { - return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); - } - @action setupDragLines = (snapToDraggedDoc: boolean = false) => { const activeDocs = this.getActiveDocuments(); - if (activeDocs.length > 50) { - DragManager.SetSnapLines([], []); - return; - } const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); - const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { - if (this.intersectRect(docDims(doc), rect)) { - snappableDocs.push(doc); - } - }; - const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to; + const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => intersectRect(docDims(doc), rect); + const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - this.getActiveDocuments().filter(doc => !doc._isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to - !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to - !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs + let snappableDocs = activeDocs.filter(doc => !StrListCast(doc.layers).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to + !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs const horizLines: number[] = []; const vertLines: number[] = []; + const invXf = this.getTransform().inverse(); snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { const { left, top, width, height } = docDims(doc); - const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); - const docSize = this.getTransform().inverse().transformDirection(width, height); + const topLeftInScreen = invXf.transformPoint(left, top); + const docSize = invXf.transformDirection(width, height); horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line }); DragManager.SetSnapLines(horizLines, vertLines); } + onPointerOver = (e: React.PointerEvent) => { - if (SnappingManager.GetIsDragging()) { - this.setupDragLines(e.ctrlKey || e.shiftKey); - } + (DocumentDecorations.Instance.Interacting || (this.props.layerProvider?.(this.props.Document) !== false && SnappingManager.GetIsDragging())) && this.setupDragLines(e.ctrlKey || e.shiftKey); e.stopPropagation(); } - @observable private _hLines: number[] | undefined; - @observable private _vLines: number[] | undefined; - private childViews = () => { const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; return [ @@ -1419,13 +1378,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />); return eles; } + @computed get placeholder() { return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}> <span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span> </div>; } - _nudgeTime = 0; nudge = action((x: number, y: number) => { if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out... @@ -1443,7 +1402,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); } - @computed get grid() { + @computed get backgroundGrid() { const gridSpace = this.chooseGridSpace(NumCast(this.layoutDoc["_backgroundGrid-spacing"], 50)); const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.panX() % gridSpace - gridSpace) * this.zoomScaling(); const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.panY() % gridSpace - gridSpace) * this.zoomScaling(); @@ -1475,11 +1434,24 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } }} />; } + + trySelectCluster = (addToSel: boolean) => { + if (this._hitCluster !== -1) { + !addToSel && SelectionManager.DeselectAll(); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster); + this.selectDocuments(eles); + return true; + } + return false; + } + @computed get marqueeView() { return <MarqueeView {...this.props} - nudge={this.isAnnotationOverlay ? undefined : this.nudge} + ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined} + nudge={this.isAnnotationOverlay || this.props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} + trySelectCluster={this.trySelectCluster} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} @@ -1488,8 +1460,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> <div ref={this._marqueeRef}> - {this.layoutDoc["_backgroundGrid-show"] ? this.grid : (null)} + {this.layoutDoc["_backgroundGrid-show"] && (!SnappingManager.GetIsDragging() || !Doc.UserDoc().showSnapLines) ? this.backgroundGrid : (null)} <CollectionFreeFormViewPannableContents + isAnnotationOverlay={this.isAnnotationOverlay} centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} presPaths={BoolCast(this.Document.presPathView)} @@ -1501,20 +1474,19 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P {this.children} </CollectionFreeFormViewPannableContents> </div> - {this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)} + {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)} </MarqueeView>; } - @computed get contentScaling() { - if (this.props.annotationsKey && !this.props.forceScaling) return 0; - const nw = returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.Document)); - const nh = returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.Document)); + if (this.props.isAnnotationOverlay && !this.props.forceScaling) return 0; + const nw = this.nativeWidth; + const nh = this.nativeHeight; const hscale = nh ? this.props.PanelHeight() / nh : 1; const wscale = nw ? this.props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } - @computed get backgroundEvents() { return this.layoutDoc._isBackground && SnappingManager.GetIsDragging(); } + @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } render() { TraceMobx(); @@ -1535,7 +1507,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P width: this.contentScaling ? `${100 / this.contentScaling}%` : "", height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> - {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? + {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ? this.placeholder : this.marqueeView} {!this.props.noOverlay ? <CollectionFreeFormOverlayView elements={this.elementFunc} /> : (null)} @@ -1556,6 +1528,17 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)} </svg> </div>} + + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ? + <div className="collectionFreeForm-groupDropper" ref={this.createDashEventsTarget} style={{ + width: this.ChildDrag ? "10000" : "100%", + height: this.ChildDrag ? "10000" : "100%", + left: this.ChildDrag ? "-5000" : 0, + top: this.ChildDrag ? "-5000" : 0, + position: "absolute", + background: "#0009930", + pointerEvents: "all" + }} /> : (null)} </div >; } } @@ -1583,6 +1566,7 @@ interface CollectionFreeFormViewPannableContentsProps { presPaths?: boolean; progressivize?: boolean; presPinView?: boolean; + isAnnotationOverlay: boolean | undefined; } @observer @@ -1728,6 +1712,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`, transition: this.props.transition, + width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection //willChange: "transform" }}> {this.props.children()} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 7040b5e56..d20d1abfc 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,31 +1,33 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, DocListCast, Opt } from "../../../../fields/Doc"; +import { AclAddonly, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils } from "../../../../Utils"; +import { Utils, intersectRect, returnFalse } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { ContextMenu } from "../../ContextMenu"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; +import { PresBox, PresMovement } from "../../nodes/PresBox"; import { PreviewCursor } from "../../PreviewCursor"; import { CollectionDockingView } from "../CollectionDockingView"; import { SubCollectionViewProps } from "../CollectionSubView"; -import { CollectionView, CollectionViewType } from "../CollectionView"; +import { CollectionView } from "../CollectionView"; import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import { Id } from "../../../../fields/FieldSymbols"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { PresBox, PresMovement } from "../../nodes/PresBox"; +import { StyleLayers } from "../../StyleProvider"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -34,23 +36,33 @@ interface MarqueeViewProps { selectDocuments: (docs: Doc[]) => void; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; + trySelectCluster: (addToSel: boolean) => boolean; nudge?: (x: number, y: number) => boolean; + ungroup?: () => void; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } - @observer export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { + private _commandExecuted = false; @observable public static DragMarquee = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @observable _downX: number = 0; @observable _downY: number = 0; @observable _visible: boolean = false; - _commandExecuted = false; - @observable _pointsX: number[] = []; - @observable _pointsY: number[] = []; - @observable _freeHand: boolean = false; + @observable _lassoPts: [number, number][] = []; + @observable _lassoFreehand: boolean = false; + + @computed get Transform() { return this.props.getTransform(); } + @computed get Bounds() { + const topLeft = this.Transform.transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY); + const size = this.Transform.transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; + } + get inkDoc() { return this.props.Document; } + get ink() { return Cast(this.props.Document.ink, InkField); } + set ink(value: Opt<InkField>) { this.props.Document.ink = value; } componentDidMount() { this.props.setPreviewCursor?.(this.setPreviewCursor); @@ -63,11 +75,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque document.removeEventListener("pointermove", this.onPointerMove, true); } document.removeEventListener("keydown", this.marqueeCommand, true); - if (hideMarquee) { - this._visible = false; - } - this._pointsX = []; - this._pointsY = []; + hideMarquee && this.hideMarquee(); + + this._lassoPts = []; } @undoBatch @@ -76,7 +86,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque //make textbox and add it to this collection // tslint:disable-next-line:prefer-const const cm = ContextMenu.Instance; - const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); + const [x, y] = this.Transform.transformPoint(this._downX, this._downY); if (e.key === "?") { cm.setDefaultItem("?", (str: string) => this.props.addDocTab( Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _fitWidth: true, _width: 400, x, y, _height: 512, _nativeWidth: 850, isAnnotating: false, title: "bing", useCors: true }), "add:right")); @@ -84,8 +94,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque cm.displayMenu(this._downX, this._downY); e.stopPropagation(); } else - if (e.key === ":") { - DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y); + if (e.key === "u" && this.props.ungroup) { + e.stopPropagation(); + this.props.ungroup(); + } + else if (e.key === ":") { + DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y); cm.displayMenu(this._downX, this._downY); e.stopPropagation(); @@ -113,8 +127,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque ns.map(line => { const indent = line.search(/\S|$/); const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: ypos, title: line }); - this.props.addDocument(newBox); - ypos += 40 * this.props.getTransform().Scale; + this.props.addDocument?.(newBox); + ypos += 40 * this.Transform.Scale; }); })(); e.stopPropagation(); @@ -135,11 +149,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque slide.x = x; slide.y = y; FormattedTextBox.SelectOnLoad = slide[Id]; - this.props.addDocument(slide); + this.props.addDocument?.(slide); //setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false)); e.stopPropagation(); - } else if (!e.ctrlKey && !e.metaKey) { - FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.ChildLayoutString ? e.key : ""; + } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { + FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout && !this.props.childLayoutString ? e.key : ""; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0)); e.stopPropagation(); @@ -183,9 +197,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 }); - this.props.addDocument(newCol); + this.props.addDocument?.(newCol); } } + @action onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.clientX; @@ -209,13 +224,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque onPointerMove = (e: PointerEvent): void => { this._lastX = e.pageX; this._lastY = e.pageY; - this._pointsX.push(e.clientX); - this._pointsY.push(e.clientY); + this._lassoPts.push([e.clientX, e.clientY]); if (!e.cancelBubble) { if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) { if (!this._commandExecuted) { - this._visible = true; + this.showMarquee(); } e.stopPropagation(); e.preventDefault(); @@ -266,6 +280,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque e.preventDefault(); } } + clearSelection() { if (window.getSelection) { window.getSelection()?.removeAllRanges(); } else if (document.getSelection()) { document.getSelection()?.empty(); } @@ -285,7 +300,9 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this._downX = x; this._downY = y; const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); - if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge); + if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) { + PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge); + } this.clearSelection(); } }); @@ -297,7 +314,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (Doc.GetSelectedTool() === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; - !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false); + if (!(e.nativeEvent as any).formattedHandled) { + if (!this.props.trySelectCluster(e.shiftKey)) { + this.setPreviewCursor(e.clientX, e.clientY, false); + } else e.stopPropagation(); + } } } // let the DocumentView stopPropagation of this event when it selects this document @@ -307,66 +328,36 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } } - intersectRect(r1: { left: number, top: number, width: number, height: number }, - r2: { left: number, top: number, width: number, height: number }) { - return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); - } - - @computed - get Bounds() { - const left = this._downX < this._lastX ? this._downX : this._lastX; - const top = this._downY < this._lastY ? this._downY : this._lastY; - const topLeft = this.props.getTransform().transformPoint(left, top); - const size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; - } - - get inkDoc() { - return this.props.Document; - } - - get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. - return Cast(this.props.Document.ink, InkField); - } - - set ink(value: InkField | undefined) { - this.props.Document.ink = value; - } - @action - showMarquee = () => { - this._visible = true; - } + showMarquee = () => { this._visible = true; } @action - hideMarquee = () => { - this._visible = false; - } + hideMarquee = () => { this._visible = false; } @undoBatch @action delete = () => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(doc => this.props.removeDocument(doc)); + selected.forEach(doc => this.props.removeDocument?.(doc)); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } - getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, _isBackground?: boolean) => { + getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, layers: string[], makeGroup: Opt<boolean>) => { const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List<Doc>(selected); - Doc.GetProto(doc).title = "nested freeform"; + Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; doc._panX = doc._panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newCollection.system = undefined; - newCollection._isBackground = _isBackground; - newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : _isBackground ? "cyan" : undefined; + newCollection.layers = new List<string>(layers); newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; + newCollection._isGroup = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; selected.forEach(d => d.context = newCollection); @@ -378,30 +369,30 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - selected.forEach(d => this.props.removeDocument(d)); + selected.forEach(d => this.props.removeDocument?.(d)); const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); - this.props.addDocument(newCollection!); + this.props.addDocument?.(newCollection!); this.props.selectDocuments([newCollection!]); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } - @undoBatch @action + @undoBatch + @action pinWithView = (e: KeyboardEvent | React.PointerEvent | undefined) => { const doc = this.props.Document; - const bounds = this.Bounds; - const selected = this.marqueeSelect(false); const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; if (curPres) { if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; pinDoc.presMovement = PresMovement.Zoom; + pinDoc.groupWithUp = false; pinDoc.context = curPres; pinDoc.title = doc.title + " - Slide"; - const presArray: Doc[] = PresBox.Instance?.sortArray(); - const size: number = PresBox.Instance?._selectedArray.size; - const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; + const presArray = PresBox.Instance?.sortArray(); + const size = PresBox.Instance?._selectedArray.size; + const presSelected = presArray && size ? presArray[size - 1] : undefined; Doc.AddDocToList(curPres, "data", pinDoc, presSelected); if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; if (!DocumentManager.Instance.getDocumentView(curPres)) { @@ -412,14 +403,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const index = PresBox.Instance?.childDocs.indexOf(pinDoc); index && (curPres._itemIndex = index); if (e instanceof KeyboardEvent ? e.key === "c" : true) { - const x = this.Bounds.left + this.Bounds.width / 2; - const y = this.Bounds.top + this.Bounds.height / 2; - const panelWidth: number = this.props.PanelWidth(); - const panelHeight: number = this.props.PanelHeight(); - const scale = Math.min(Number(panelWidth) / this.Bounds.width, Number(panelHeight) / this.Bounds.height); + const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height); pinDoc.presPinView = true; - pinDoc.presPinViewX = x; - pinDoc.presPinViewY = y; + pinDoc.presPinViewX = this.Bounds.left + this.Bounds.width / 2; + pinDoc.presPinViewY = this.Bounds.top + this.Bounds.height / 2; pinDoc.presPinViewScale = scale; } } @@ -427,11 +414,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.hideMarquee(); } - @undoBatch @action - collection = (e: KeyboardEvent | React.PointerEvent | undefined) => { - const bounds = this.Bounds; + @undoBatch + @action + collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => { const selected = this.marqueeSelect(false); - if (e instanceof KeyboardEvent ? e.key === "c" : true) { + if (e instanceof KeyboardEvent ? "cg".includes(e.key) : true) { selected.map(action(d => { const dx = NumCast(d.x); const dy = NumCast(d.y); @@ -439,46 +426,36 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque delete d.y; delete d.activeFrame; delete d.displayTimecode; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - d.x = dx - bounds.left - bounds.width / 2; - d.y = dy - bounds.top - bounds.height / 2; + d.x = dx - this.Bounds.left - this.Bounds.width / 2; + d.y = dy - this.Bounds.top - this.Bounds.height / 2; return d; })); - this.props.removeDocument(selected); + this.props.removeDocument?.(selected); } - const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined); - this.props.addDocument(newCollection); + const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, [], group); + this.props.addDocument?.(newCollection); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } - @undoBatch @action + @undoBatch + @action syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); if (e instanceof KeyboardEvent ? e.key === "i" : true) { - const inks = selected.filter(s => s.proto?.type === "ink"); - const setDocs = selected.filter(s => s.proto?.type === "text" && s.color); - const sets = setDocs.map((sd) => { - return Cast(sd.data, RichTextField)?.Text as string; - }); + const inks = selected.filter(s => s.proto?.type === DocumentType.INK); + const setDocs = selected.filter(s => s.proto?.type === DocumentType.RTF && s.color); + const sets = setDocs.map((sd) => Cast(sd.data, RichTextField)?.Text as string); const colors = setDocs.map(sd => FieldValue(sd.color) as string); const wordToColor = new Map<string, string>(); - sets.forEach((st: string, i: number) => { - const words = st.split(","); - words.forEach(word => { - wordToColor.set(word, colors[i]); - }); - }); + sets.forEach((st: string, i: number) => st.split(",").forEach(word => wordToColor.set(word, colors[i]))); const strokes: InkData[] = []; - inks.forEach(i => { - const d = Cast(i.data, InkField); - const x = NumCast(i.x); - const y = NumCast(i.y); + inks.filter(i => Cast(i.data, InkField)).forEach(i => { + const d = Cast(i.data, InkField, null); const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); - if (d) { - strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); - } + strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top }))); }); CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { // const wordResults = results.filter((r: any) => r.category === "inkWord"); @@ -523,23 +500,21 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque // } const lines = results.filter((r: any) => r.category === "line"); const text = lines.map((l: any) => l.recognizedText).join("\r\n"); - this.props.addDocument(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); + this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); }); } } - @undoBatch @action + @undoBatch + @action summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { - const bounds = this.Bounds; - const selected = this.marqueeSelect(false); - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left; - d.y = NumCast(d.y) - bounds.top; - d.page = -1; + const selected = this.marqueeSelect(false).map(d => { + this.props.removeDocument?.(d); + d.x = NumCast(d.x) - this.Bounds.left; + d.y = NumCast(d.y) - this.Bounds.top; return d; }); - const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); + const summary = Docs.Create.TextDocument("", { x: this.Bounds.left, y: this.Bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); const portal = Doc.MakeAlias(summary); Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected); Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations"); @@ -551,18 +526,18 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.props.addLiveTextDocument(summary); MarqueeOptionsMenu.Instance.fadeOut(true); } + @action background = (e: KeyboardEvent | React.PointerEvent | undefined) => { - const newCollection = this.getCollection([], undefined, true); - this.props.addDocument(newCollection); + const newCollection = this.getCollection([], undefined, [StyleLayers.Background], undefined); + this.props.addDocument?.(newCollection); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - setTimeout(() => this.props.selectDocuments([newCollection]), 0); + setTimeout(() => this.props.selectDocuments([newCollection])); } @undoBatch - @action - marqueeCommand = async (e: KeyboardEvent) => { + marqueeCommand = action((e: KeyboardEvent) => { if (this._commandExecuted || (e as any).propagationIsStopped) { return; } @@ -573,83 +548,31 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.delete(); e.stopPropagation(); } - if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S" || e.key === "p") { + if ("cbtsSpg".indexOf(e.key) !== -1) { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); (e as any).propagationIsStopped = true; - if (e.key === "c" || e.key === "t") { - this.collection(e); - } - if (e.key === "s" || e.key === "S") { - this.summary(e); - } - if (e.key === "b") { - this.background(e); - } - if (e.key === "p") { - this.pileup(e); - } + if (e.key === "g") this.collection(e, true); + if (e.key === "c" || e.key === "t") this.collection(e); + if (e.key === "s" || e.key === "S") this.summary(e); + if (e.key === "b") this.background(e); + if (e.key === "p") this.pileup(e); this.cleanupInteractions(false); } if (e.key === "r" || e.key === " ") { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); - this.changeFreeHand(true); + this._lassoFreehand = !this._lassoFreehand; } - } + }); - @action - changeFreeHand = (x: boolean) => { - this._freeHand = !this._freeHand; - } - // @action - // marqueeInkSelect(ink: Map<any, any>) { - // let idata = new Map(); - // let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin. - // let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2); - // ink.forEach((value: PointData, key: string, map: any) => { - // if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { - // // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling()); - // idata.set(key, - // { - // pathData: value.pathData.map(val => { - // let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y); - // return { x: tVal[0], y: tVal[1] }; - // // return { x: val.x + centerShiftX, y: val.y + centerShiftY } - // }), - // color: value.color, - // width: value.width, - // tool: value.tool, - // page: -1 - // }); - // } - // }); - // // InkSelectDecorations.Instance.SetSelected(idata); - // return idata; - // } - - // @action - // marqueeInkDelete(ink?: Map<any, any>) { - // // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB. - // // ink.forEach((value: StrokeData, key: string, map: any) => - // // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key)); - - // if (ink) { - // let idata = new Map(); - // ink.forEach((value: PointData, key: string, map: any) => - // !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - // this.ink = new InkField(idata); - // } - // } touchesLine(r1: { left: number, top: number, width: number, height: number }) { - for (var i = 0; i < this._pointsX.length; i++) { - const topLeft = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]); - if (topLeft[0] > r1.left && - topLeft[0] < r1.left + r1.width && - topLeft[1] > r1.top && - topLeft[1] < r1.top + r1.height) { + for (const lassoPt of this._lassoPts) { + const topLeft = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); + if (r1.left < topLeft[0] && topLeft[0] < r1.left + r1.width && + r1.top < topLeft[1] && topLeft[1] < r1.top + r1.height) { return true; } } @@ -657,30 +580,22 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } boundingShape(r1: { left: number, top: number, width: number, height: number }) { - const trueLeft = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[0]; - const trueTop = this.props.getTransform().transformPoint(Math.min(...this._pointsX), Math.min(...this._pointsY))[1]; - const trueRight = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[0]; - const trueBottom = this.props.getTransform().transformPoint(Math.max(...this._pointsX), Math.max(...this._pointsY))[1]; - - if (r1.left > trueLeft && r1.top > trueTop && r1.left + r1.width < trueRight && r1.top + r1.height < trueBottom) { - var hasTop = false; - var hasLeft = false; - var hasBottom = false; - var hasRight = false; - for (var i = 0; i < this._pointsX.length; i++) { - const truePoint = this.props.getTransform().transformPoint(this._pointsX[i], this._pointsY[i]); - if (!hasLeft && (truePoint[0] > trueLeft && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) { - hasLeft = true; - } - if (!hasTop && (truePoint[1] > trueTop && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) { - hasTop = true; - } - if (!hasRight && (truePoint[0] < trueRight && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height)) { - hasRight = true; - } - if (!hasBottom && (truePoint[1] < trueBottom && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width)) { - hasBottom = true; - } + const xs = this._lassoPts.map(pair => pair[0]); + const ys = this._lassoPts.map(pair => pair[1]); + const tl = this.Transform.transformPoint(Math.min(...xs), Math.min(...ys)); + const br = this.Transform.transformPoint(Math.max(...xs), Math.max(...ys)); + + if (r1.left > tl[0] && r1.top > tl[1] && r1.left + r1.width < br[0] && r1.top + r1.height < br[1]) { + let hasTop = false; + let hasLeft = false; + let hasBottom = false; + let hasRight = false; + for (const lassoPt of this._lassoPts) { + const truePoint = this.Transform.transformPoint(lassoPt[0], lassoPt[1]); + hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); + hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); + hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); + hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); if (hasTop && hasLeft && hasBottom && hasRight) { return true; } @@ -688,111 +603,45 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } return false; } + marqueeSelect(selectBackgrounds: boolean = true) { - const selRect = this.Bounds; const selection: Doc[] = []; - this.props.activeDocuments().filter(doc => !doc._isBackground && !doc.z).map(doc => { + const selectFunc = (doc: Doc) => { const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this._freeHand === false) { - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { - selection.push(doc); - } + const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) }; + if (!this._lassoFreehand) { + intersectRect(bounds, this.Bounds) && selection.push(doc); } else { - if (this.touchesLine({ left: x, top: y, width: w, height: h }) || - this.boundingShape({ left: x, top: y, width: w, height: h })) { - selection.push(doc); - } + (this.touchesLine(bounds) || this.boundingShape(bounds)) && selection.push(doc); } - }); - if (!selection.length && selectBackgrounds) { - this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { - selection.push(doc); - } - }); - } - if (!selection.length) { - const left = this._downX < this._lastX ? this._downX : this._lastX; - const top = this._downY < this._lastY ? this._downY : this._lastY; - const topLeft = this.props.getContainerTransform().transformPoint(left, top); - const size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - const otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; - this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - const layoutDoc = Doc.Layout(doc); - const x = NumCast(doc.x); - const y = NumCast(doc.y); - const w = NumCast(layoutDoc._width); - const h = NumCast(layoutDoc._height); - if (this._freeHand === false) { - if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { - selection.push(doc); - } - } else { - if (this.touchesLine({ left: x, top: y, width: w, height: h }) || - this.boundingShape({ left: x, top: y, width: w, height: h })) { - selection.push(doc); - } - } - }); - } + }; + this.props.activeDocuments().filter(doc => this.props.layerProvider?.(doc) !== false && !doc.z).map(selectFunc); + if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc); + if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc); return selection; } - @computed - get marqueeDiv() { - const p = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; - const v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - /** - * @RE - The commented out span below - * This contains the "C for collection, ..." text on marquees. - * Commented out by syip2 when the marquee menu was added. - */ - if (!this._freeHand) { - return <div className="marquee" style={{ - transform: `translate(${p[0]}px, ${p[1]}px)`, - width: `${Math.abs(v[0])}`, - height: `${Math.abs(v[1])}`, zIndex: 2000 - }} > - <span className="marquee-legend"></span> - </div>; - - } else { - var str: string = ""; - for (var i = 0; i < this._pointsX.length; i++) { - const pt = this.props.getContainerTransform().transformPoint(this._pointsX[i], this._pointsY[i]); - str += pt[0].toString(); - str += ","; - str += pt[1].toString(); - str += (" "); - } - - //hardcoded height and width. - return <div className="marquee" style={{ zIndex: 2000 }}> - <svg height={2000} width={2000}> - <polyline - points={str} - fill="none" - stroke="black" - strokeWidth="1" - strokeDasharray="3" - /> - </svg> - </div>; - } + @computed get marqueeDiv() { + const cpt = this._lassoFreehand || !this._visible ? [0, 0] : [this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY]; + const p = this.props.getContainerTransform().transformPoint(cpt[0], cpt[1]); + const v = this._lassoFreehand ? [0, 0] : this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); + return <div className="marquee" style={{ + transform: `translate(${p[0]}px, ${p[1]}px)`, + width: Math.abs(v[0]), + height: Math.abs(v[1]), + zIndex: 2000 + }}> {this._lassoFreehand ? + <svg height={2000} width={2000}> + <polyline points={this._lassoPts.reduce((s, pt) => s + pt[0] + "," + pt[1] + " ", "")} fill="none" stroke="black" strokeWidth="1" strokeDasharray="3" /> + </svg> + : + <span className="marquee-legend" />} + </div>; } render() { return <div className="marqueeView" - style={{ overflow: !this.props.ContainingCollectionView && this.props.annotationsKey ? "visible" : StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }} + style={{ overflow: (!this.props.ContainingCollectionView && this.props.isAnnotationOverlay) ? "visible" : StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }} onDragOver={e => e.preventDefault()} onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}> {this._visible ? this.marqueeDiv : null} |
