diff options
| author | Sam Wilkins <samwilkins333@gmail.com> | 2019-11-19 11:28:49 -0500 |
|---|---|---|
| committer | Sam Wilkins <samwilkins333@gmail.com> | 2019-11-19 11:28:49 -0500 |
| commit | 3f86ea5556ab7a1fc13cd9c74ef4305f2f5cd778 (patch) | |
| tree | 4d13f3401ecc80fb5bce22d2a3382545102fda7f /src/client/views/collections/collectionFreeForm | |
| parent | 611bb858265b6667f2b7db858d183cea16f273aa (diff) | |
| parent | d259cf7b16e64794bc12ae420f9b512a75eee46c (diff) | |
merged with master
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
10 files changed, 655 insertions, 426 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 886692172..48d330674 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -63,11 +63,12 @@ export function computePivotLayout(pivotDoc: Doc, childDocs: Doc[], childPairs: fontSize: NumCast(pivotDoc.pivotFontSize, 10) }); for (const doc of val) { + let layoutDoc = Doc.Layout(doc); docMap.set(doc, { x: x + xCount * pivotAxisWidth * 1.25, y: -y, width: pivotAxisWidth, - height: doc.nativeWidth ? (NumCast(doc.nativeHeight) / NumCast(doc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth + height: layoutDoc.nativeWidth ? (NumCast(layoutDoc.nativeHeight) / NumCast(layoutDoc.nativeWidth)) * pivotAxisWidth : pivotAxisWidth }); xCount++; if (xCount >= numCols) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss index cfd18ad35..75af11537 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss @@ -1,20 +1,18 @@ .collectionfreeformlinkview-linkLine { stroke: black; - transform: translate(10000px,10000px); opacity: 0.8; pointer-events: all; stroke-width: 3px; + transition: opacity 0.5s ease-in; } .collectionfreeformlinkview-linkCircle { stroke: rgb(0,0,0); opacity: 0.5; - transform: translate(10000px,10000px); pointer-events: all; cursor: pointer; } .collectionfreeformlinkview-linkText { stroke: rgb(0,0,0); opacity: 0.5; - transform: translate(10000px,10000px); pointer-events: all; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index fe92eed10..837413842 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,63 +1,51 @@ import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc"; -import { BoolCast, NumCast, StrCast } from "../../../../new_fields/Types"; -import { InkingControl } from "../../InkingControl"; +import { Doc } from "../../../../new_fields/Doc"; +import { Utils } from '../../../../Utils'; +import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; import React = require("react"); import v5 = require("uuid/v5"); +import { DocumentType } from "../../../documents/DocumentTypes"; +import { observable, action } from "mobx"; export interface CollectionFreeFormLinkViewProps { - A: Doc; - B: Doc; + A: DocumentView; + B: DocumentView; LinkDocs: Doc[]; - addDocument: (document: Doc) => boolean; - removeDocument: (document: Doc) => boolean; } @observer export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> { - - onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0 && !InkingControl.Instance.selectedTool) { - let a = this.props.A; - let b = this.props.B; - let x1 = NumCast(a.x) + (BoolCast(a.isMinimized) ? 5 : a[WidthSym]() / 2); - let y1 = NumCast(a.y) + (BoolCast(a.isMinimized) ? 5 : a[HeightSym]() / 2); - let x2 = NumCast(b.x) + (BoolCast(b.isMinimized) ? 5 : b[WidthSym]() / 2); - let y2 = NumCast(b.y) + (BoolCast(b.isMinimized) ? 5 : b[HeightSym]() / 2); - // this.props.LinkDocs.map(l => { - // let width = l[WidthSym](); - // l.x = (x1 + x2) / 2 - width / 2; - // l.y = (y1 + y2) / 2 + 10; - // if (!this.props.removeDocument(l)) this.props.addDocument(l, false); - // }); - e.stopPropagation(); - e.preventDefault(); - } + @observable _alive: number = 0; + @observable _opacity: number = 1; + @action + componentDidMount() { + this._alive = 1; + setTimeout(this.rerender, 50); + setTimeout(action(() => this._opacity = 0.05), 50); + } + @action + componentWillUnmount() { + this._alive = 0; } + rerender = action(() => { + if (this._alive) { + setTimeout(this.rerender, 50); + this._alive++; + } + }); + render() { - // let l = this.props.LinkDocs; - let a = this.props.A; - let b = this.props.B; - let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2); - let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2); - let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2); - let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2); - let text = ""; - // let first = this.props.LinkDocs[0]; - // if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); - // else text = "-multiple-"; - return ( - <> - <line key="linkLine" className="collectionfreeformlinkview-linkLine" - x1={`${x1}`} y1={`${y1}`} - x2={`${x2}`} y2={`${y2}`} /> - {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle" - cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={8} onPointerDown={this.onPointerDown} /> */} - <text key="linkText" textAnchor="middle" className="collectionfreeformlinkview-linkText" x={`${(x1 + x2) / 2}`} y={`${(y1 + y2) / 2}`}> - {text} - </text> - </> - ); + let y = this._alive; + let acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + let bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : []; + let a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect(); + let b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect(); + let pt1 = Utils.getNearestPointInPerimeter(a.left, a.top, a.width, a.height, b.left + b.width / 2, b.top + b.height / 2); + let pt2 = Utils.getNearestPointInPerimeter(b.left, b.top, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2); + return (<line key="linkLine" className="collectionfreeformlinkview-linkLine" + style={{ opacity: this._opacity }} + x1={`${pt1[0]}`} y1={`${pt1[1]}`} + x2={`${pt2[0]}`} y2={`${pt2[1]}`} />); } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss index 30e158603..cb5cef29c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss @@ -1,10 +1,9 @@ .collectionfreeformlinksview-svgCanvas{ - transform: translate(-10000px,-10000px); position: absolute; top: 0; left: 0; - width: 20000px; - height: 20000px; + width: 100%; + height: 100%; pointer-events: none; } .collectionfreeformlinksview-container { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index a81f5315a..e9191c176 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,19 +1,18 @@ -import { computed, IReactionDisposer, reaction } from "mobx"; +import { computed, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { List } from "../../../../new_fields/List"; -import { listSpec } from "../../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; -import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); +import { Utils } from "../../../../Utils"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { DocumentType } from "../../../documents/DocumentTypes"; @observer -export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> { +export class CollectionFreeFormLinksView extends React.Component { _brushReactionDisposer?: IReactionDisposer; componentDidMount() { @@ -69,59 +68,33 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP // }); } componentWillUnmount() { - if (this._brushReactionDisposer) { - this._brushReactionDisposer(); - } + this._brushReactionDisposer && this._brushReactionDisposer(); } - documentAnchors(view: DocumentView) { - let equalViews = [view]; - let containerDoc = FieldValue(Cast(view.props.Document.annotationOn, Doc)); - if (containerDoc) { - equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!); - } - if (view.props.ContainingCollectionDoc) { - let collid = view.props.ContainingCollectionDoc[Id]; - DocListCast(this.props.Document[this.props.fieldKey]). - filter(child => - child[Id] === collid).map(view => - DocumentManager.Instance.getDocumentViews(view).map(view => - equalViews.push(view))); - } - return equalViews.filter(sv => sv.props.ContainingCollectionDoc === this.props.Document); - } - @computed get uniqueConnections() { let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => { - let srcViews = this.documentAnchors(connection.a); - let targetViews = this.documentAnchors(connection.b); - - let possiblePairs: { a: Doc, b: Doc, }[] = []; - srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document }))); - possiblePairs.map(possiblePair => { - if (!drawnPairs.reduce((found, drawnPair) => { - let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b)); - let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a)); - let match = match1 || match2; - if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { - drawnPair.l.push(connection.l); - } - return match || found; - }, false)) { - drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] }); + if (!drawnPairs.reduce((found, drawnPair) => { + let match1 = (connection.a === drawnPair.a && connection.b === drawnPair.b); + let match2 = (connection.a === drawnPair.b && connection.b === drawnPair.a); + let match = match1 || match2; + if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) { + drawnPair.l.push(connection.l); } - }); + return match || found; + }, false)) { + drawnPairs.push({ a: connection.a, b: connection.b, l: [connection.l] }); + } return drawnPairs; - }, [] as { a: Doc, b: Doc, l: Doc[] }[]); - return connections.map(c => <CollectionFreeFormLinkView key={c.l.reduce((p, l) => p + l[Id], "")} A={c.a} B={c.b} LinkDocs={c.l} - removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />); + }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); + return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) // get rid of the filter to show links to documents in addition to document anchors + .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); } render() { return ( <div className="collectionfreeformlinksview-container"> <svg className="collectionfreeformlinksview-svgCanvas"> - {this.uniqueConnections} + {SelectionManager.GetIsDragging() ? (null) : this.uniqueConnections} </svg> {this.props.children} </div> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index bb1a12f88..db36c4391 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -17,6 +17,7 @@ width: 100%; height: 100%; transform-origin: left top; + touch-action: none; } .collectionFreeform-customText { @@ -25,6 +26,9 @@ } .collectionfreeformview-container { + // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions. + touch-action: none; + .collectionfreeformview>.jsx-parser { position: inherit; height: 100%; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index adbad5da5..3b313c34a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,16 +1,16 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable } from "mobx"; +import { action, computed, observable, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { InkField, PointData, InkTool } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { ScriptField } from "../../../../new_fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types"; import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; -import { aggregateBounds, emptyFunction, intersectRect, returnEmptyString, returnOne, Utils } from "../../../../Utils"; +import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; @@ -25,19 +25,22 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingCanvas } from "../../InkingCanvas"; -import { CollectionFreeFormDocumentView, positionSchema } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocumentContentsView } from "../../nodes/DocumentContentsView"; -import { documentSchema, DocumentViewProps } from "../../nodes/DocumentView"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; -import PDFMenu from "../../pdf/PDFMenu"; import { CollectionSubView } from "../CollectionSubView"; import { computePivotLayout, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; -import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { InteractionUtils } from "../../../util/InteractionUtils"; +import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; +import PDFMenu from "../../pdf/PDFMenu"; +import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; +import { InkingControl } from "../../InkingControl"; +import { InkingStroke, CreatePolyline } from "../../InkingStroke"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -51,6 +54,11 @@ export const panZoomSchema = createSchema({ isRuleProvider: "boolean", fitToBox: "boolean", panTransformType: "string", + scrollHeight: "number", + fitX: "number", + fitY: "number", + fitW: "number", + fitH: "number" }); type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; @@ -67,9 +75,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @computed get fitToContent() { return (this.props.fitToBox || this.Document.fitToBox) && !this.isAnnotationOverlay; } @computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; } @computed get contentBounds() { return aggregateBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)); } - @computed get nativeWidth() { return this.fitToContent ? 0 : this.Document.nativeWidth || 0; } + @computed get nativeWidth() { return this.Document.fitToContent ? 0 : this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.fitToContent ? 0 : this.Document.nativeHeight || 0; } - private get isAnnotationOverlay() { return this.props.fieldExt ? true : false; } // fieldExt will be "" or "annotation". should maybe generalize this, or make it more specific (ie, 'annotation' instead of 'fieldExt') + private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } private easing = () => this.props.Document.panTransformType === "Ease"; private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document.panX || 0; @@ -111,10 +119,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); } - @computed get fieldExtensionDoc() { - return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey); - } - @action onDrop = (e: React.DragEvent): Promise<void> => { var pt = this.getTransform().transformPoint(e.pageX, e.pageY); @@ -131,21 +135,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let z = NumCast(de.data.droppedDocuments[0].z); + let firstDoc = de.data.droppedDocuments[0]; + let z = NumCast(firstDoc.z); let x = (z ? xpo : xp) - de.data.offset[0]; let y = (z ? ypo : yp) - de.data.offset[1]; - let dropX = NumCast(de.data.droppedDocuments[0].x); - let dropY = NumCast(de.data.droppedDocuments[0].y); + let dropX = NumCast(firstDoc.x); + let dropY = NumCast(firstDoc.y); de.data.droppedDocuments.forEach(action((d: Doc) => { + let layoutDoc = Doc.Layout(d); d.x = x + NumCast(d.x) - dropX; d.y = y + NumCast(d.y) - dropY; - if (!NumCast(d.width)) { - d.width = 300; + if (!NumCast(layoutDoc.width)) { + layoutDoc.width = 300; } - if (!NumCast(d.height)) { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + if (!NumCast(layoutDoc.height)) { + let nw = NumCast(layoutDoc.nativeWidth); + let nh = NumCast(layoutDoc.nativeHeight); + layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); })); @@ -158,12 +164,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let dragDoc = de.data.dropDocument; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(de.data.dropDocument.x); - let dropY = NumCast(de.data.dropDocument.y); + let dropX = NumCast(dragDoc.x); + let dropY = NumCast(dragDoc.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; dragDoc.y = y + NumCast(dragDoc.y) - dropY; - de.data.targetContext = this.props.Document; - dragDoc.targetContext = this.props.Document; + de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } } @@ -173,36 +178,33 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { + let layoutDoc = Doc.Layout(cd); let cx = NumCast(cd.x) - this._clusterDistance; let cy = NumCast(cd.y) - this._clusterDistance; - let cw = NumCast(cd.width) + 2 * this._clusterDistance; - let ch = NumCast(cd.height) + 2 * this._clusterDistance; - return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? + let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; + let 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; }, -1); } - tryDragCluster(e: PointerEvent) { - let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); - if (cluster !== -1) { - let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); - - // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view - let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); - let clusterDocs = SelectionManager.SelectedDocuments(); - SelectionManager.DeselectAll(); - prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - - let 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); - de.offset = this.getTransform().transformDirection(e.x - left, e.y - top); - de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; - DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { - handlers: { dragComplete: action(emptyFunction) }, - hideSource: !de.dropAction - }); - return true; + tryDragCluster(e: PointerEvent | TouchEvent) { + let ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0); + if (ptsParent) { + let cluster = this.pickCluster(this.getTransform().transformPoint(ptsParent.clientX, ptsParent.clientY)); + if (cluster !== -1) { + let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); + let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); + let 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); + 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, { + handlers: { dragComplete: action(emptyFunction) }, + hideSource: !de.dropAction + }); + return true; + } } return false; @@ -265,109 +267,257 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return clusterColor; } + @observable private _points: { x: number, y: number }[] = []; + @action onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble) return; this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; - if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { + if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); - this._lastX = e.pageX; - this._lastY = e.pageY; + if (InkingControl.Instance.selectedTool === InkTool.None) { + this._lastX = e.pageX; + this._lastY = e.pageY; + } + else { + e.stopPropagation(); + e.preventDefault(); + + if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { + let point = this.getTransform().transformPoint(e.pageX, e.pageY); + this._points.push({ x: point[0], y: point[1] }); + } + } } } + @action + handle1PointerDown = (e: React.TouchEvent) => { + let pt = e.targetTouches.item(0); + if (pt) { + this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; + } + } + + @action onPointerUp = (e: PointerEvent): void => { + if (InteractionUtils.IsType(e, InteractionUtils.TOUCH) && this._points.length <= 1) return; + + if (this._points.length > 1) { + let B = this.svgBounds; + let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top })); + let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top }); + this.addDocument(inkDoc); + this._points = []; + } + document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener("touchmove", this.onTouch); + document.removeEventListener("touchend", this.onTouchEnd); + } + + @action + pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { + // I think it makes sense for the marquee menu to go away when panned. -syip2 + MarqueeOptionsMenu.Instance.fadeOut(true); + + let x = this.Document.panX || 0; + let y = this.Document.panY || 0; + let docs = this.childLayoutPairs.map(pair => pair.layout); + let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); + if (!this.isAnnotationOverlay) { + PDFMenu.Instance.fadeOut(true); + let minx = docs.length ? NumCast(docs[0].x) : 0; + let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + let miny = docs.length ? NumCast(docs[0].y) : 0; + let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + let ranges = docs.filter(doc => doc).reduce((range, doc) => { + let layoutDoc = Doc.Layout(doc); + let x = NumCast(doc.x); + let xe = x + NumCast(layoutDoc.width); + let y = NumCast(doc.y); + let ye = y + NumCast(layoutDoc.height); + return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], + [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; + }, [[minx, maxx], [miny, maxy]]); + let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField); + if (ink && ink.inkData) { + // ink.inkData.forEach((value: PointData, key: string) => { + // let bounds = InkingCanvas.StrokeRect(value); + // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; + // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; + // }); + } + + let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; + let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale, + this.props.PanelHeight() / this.zoomScaling() * cscale); + if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; + if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; + if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; + if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; + } + this.setPan(x - dx, y - dy); + this._lastX = e.clientX; + this._lastY = e.clientY; } @action onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble) { - if (this._hitCluster && this.tryDragCluster(e)) { - 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); - document.removeEventListener("pointerup", this.onPointerUp); - return; + if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) { + if (this.props.active()) { + e.stopPropagation(); } - let x = this.Document.panX || 0; - let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout); - let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); - if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); - let minx = docs.length ? NumCast(docs[0].x) : 0; - let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; - let miny = docs.length ? NumCast(docs[0].y) : 0; - let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; - let ranges = docs.filter(doc => doc).reduce((range, doc) => { - let x = NumCast(doc.x); - let xe = x + NumCast(doc.width); - let y = NumCast(doc.y); - let ye = y + NumCast(doc.height); - return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], - [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; - }, [[minx, maxx], [miny, maxy]]); - let ink = Cast(this.fieldExtensionDoc.ink, InkField); - if (ink && ink.inkData) { - ink.inkData.forEach((value: StrokeData, key: string) => { - let bounds = InkingCanvas.StrokeRect(value); - ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)]; - ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)]; - }); + return; + } + if (!e.cancelBubble) { + if (InkingControl.Instance.selectedTool === InkTool.None) { + if (this._hitCluster && this.tryDragCluster(e)) { + 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); + document.removeEventListener("pointerup", this.onPointerUp); + return; } - - let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; - let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale, - this.props.PanelHeight() / this.zoomScaling() * cscale); - if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2; - if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2; - if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2; - if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2; + this.pan(e); + } + else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { + let point = this.getTransform().transformPoint(e.clientX, e.clientY); + this._points.push({ x: point[0], y: point[1] }); } - this.setPan(x - dx, y - dy); - this._lastX = e.pageX; - this._lastY = e.pageY; 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(); } } + handle1PointerMove = (e: TouchEvent) => { + // panning a workspace + if (!e.cancelBubble) { + let pt = e.targetTouches.item(0); + if (pt) { + if (InkingControl.Instance.selectedTool === InkTool.None) { + if (this._hitCluster && this.tryDragCluster(e)) { + 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); + document.removeEventListener("pointerup", this.onPointerUp); + return; + } + this.pan(pt); + } + else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { + let point = this.getTransform().transformPoint(pt.clientX, pt.clientY); + this._points.push({ x: point[0], y: point[1] }); + } + } + e.stopPropagation(); + e.preventDefault(); + } + } + + handle2PointersMove = (e: TouchEvent) => { + // pinch zooming + if (!e.cancelBubble) { + let pt1: Touch | null = e.targetTouches.item(0); + let pt2: Touch | null = e.targetTouches.item(1); + if (!pt1 || !pt2) return; + + if (this.prevPoints.size === 2) { + let oldPoint1 = this.prevPoints.get(pt1.identifier); + let oldPoint2 = this.prevPoints.get(pt2.identifier); + if (oldPoint1 && oldPoint2) { + let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); + + // if zooming, zoom + if (dir !== 0) { + let d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); + let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); + let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + + // calculate the raw delta value + let rawDelta = (dir * (d1 + d2)); + + // this floors and ceils the delta value to prevent jitteriness + let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16); + this.zoom(centerX, centerY, delta); + this.prevPoints.set(pt1.identifier, pt1); + this.prevPoints.set(pt2.identifier, pt2); + } + // this is not zooming. derive some form of panning from it. + else { + // use the centerx and centery as the "new mouse position" + let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + this.pan({ clientX: centerX, clientY: centerY }); + this._lastX = centerX; + this._lastY = centerY; + } + } + } + } + e.stopPropagation(); + e.preventDefault(); + } + + handle2PointersDown = (e: React.TouchEvent) => { + let pt1: React.Touch | null = e.targetTouches.item(0); + let pt2: React.Touch | null = e.targetTouches.item(1); + if (!pt1 || !pt2) return; + + let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; + this._lastX = centerX; + this._lastY = centerY; + } + + cleanUpInteractions = () => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener("touchmove", this.onTouch); + document.removeEventListener("touchend", this.onTouchEnd); + } + + @action + zoom = (pointX: number, pointY: number, deltaY: number): void => { + let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1; + if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { + deltaScale = 1 / this.zoomScaling(); + } + if (deltaScale < 0) deltaScale = -deltaScale; + let [x, y] = this.getTransform().transformPoint(pointX, pointY); + let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); + + let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); + this.props.Document.scale = Math.abs(safeScale); + this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); + } + @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; + if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } else if (this.props.active()) { e.stopPropagation(); - let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; - if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { - deltaScale = 1 / this.zoomScaling(); - } - if (deltaScale < 0) deltaScale = -deltaScale; - let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY); - let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); - - let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); - this.props.Document.scale = Math.abs(safeScale); - this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); + this.zoom(e.clientX, e.clientY, e.deltaY); } } @action - setPan(panX: number, panY: number) { - if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) { - this.props.Document.panTransformType = "None"; + setPan(panX: number, panY: number, panType: string = "None") { + if (!this.Document.lockedTransform || this.Document.inOverlay) { + this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); - const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); - this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; - this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY; + const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); + this.Document.panX = this.isAnnotationOverlay ? newPanX : panX; + this.Document.panY = this.isAnnotationOverlay ? newPanY : panY; } } @@ -412,19 +562,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.props.Document.scrollY = NumCast(doc.y) - offset; } } else { - const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2; - const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2; + let layoutdoc = Doc.Layout(doc); + const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2; const newState = HistoryUtil.getState(); newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; - this.setPan(newPanX, newPanY); - this.Document.panTransformType = "Ease"; + this.setPan(newPanX, newPanY, "Ease"); Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); - willZoom && this.setScaleToZoom(doc, scale); + willZoom && this.setScaleToZoom(layoutdoc, scale); afterFocus && setTimeout(() => { if (afterFocus && afterFocus()) { @@ -453,6 +603,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ...this.props, DataDoc: childData, Document: childLayout, + layoutKey: undefined, ruleProvider: this.Document.isRuleProvider && childLayout.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider, //bcz: hack! - currently ruleProviders apply to documents in nested colleciton, not direct children of themselves onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, @@ -470,30 +621,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getScale: this.getScale }; } - getDocumentViewProps(layoutDoc: Doc): DocumentViewProps { - return { - ...this.props, - ScreenToLocalTransform: this.getTransform, - PanelWidth: layoutDoc[WidthSym], - PanelHeight: layoutDoc[HeightSym], - ContentScaling: returnOne, - ContainingCollectionView: this.props.ContainingCollectionView, - focus: this.focusDocument, - backgroundColor: returnEmptyString, - parentActive: this.props.active, - bringToFront: this.bringToFront, - zoomToScale: this.zoomToScale, - getScale: this.getScale - }; - } getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { const script = this.Document.arrangeScript; const result = script && script.script.run(params, console.log); + const layoutDoc = Doc.Layout(params.doc); if (result && result.success) { return { ...result, transition: "transform 1s" }; } - return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; + return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") }; } viewDefsToJSX = (views: any[]) => { @@ -610,7 +746,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. this.Document.isRuleProvider && this.childLayoutPairs.map(pair => // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) - DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + DocListCast(Doc.Layout(pair.layout).data).map(heading => { let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { @@ -621,16 +757,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } analyzeStrokes = async () => { - let data = Cast(this.fieldExtensionDoc.ink, InkField); - if (data) { - CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, ["inkAnalysis", "handwriting"], data.inkData); + const extensionDoc = this.extensionDoc; + let data = extensionDoc && Cast(extensionDoc.ink, InkField); + if (data && extensionDoc) { + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(extensionDoc, ["inkAnalysis", "handwriting"], data.inkData); } } onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => BoolCast(d.isTemplate))) { + if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) { layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); @@ -684,58 +821,64 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private childViews = () => { let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; return [ - <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />, ...children, ...this.views, ]; } + + @computed get svgBounds() { + let xs = this._points.map(p => p.x); + let ys = this._points.map(p => p.y); + let right = Math.max(...xs); + let left = Math.min(...xs); + let bottom = Math.max(...ys); + let top = Math.min(...ys); + return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; + } + + @computed get currentStroke() { + if (this._points.length <= 1) { + return (null); + } + + let B = this.svgBounds; + + return ( + <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}> + {CreatePolyline(this._points, B.left, B.top)} + </svg> + ); + } + + children = () => { + let eles: JSX.Element[] = []; + this.currentStroke && (eles.push(this.currentStroke)); + this.extensionDoc && (eles.push(...this.childViews())); + eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />); + return eles; + } render() { + trace(); // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) - this.props.Document.fitX = this.contentBounds && this.contentBounds.x; - this.props.Document.fitY = this.contentBounds && this.contentBounds.y; - this.props.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x); - this.props.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); - // if fieldExt is set, then children will be stored in the extension document for the fieldKey. + this.Document.fitX = this.contentBounds && this.contentBounds.x; + this.Document.fitY = this.contentBounds && this.contentBounds.y; + this.Document.fitW = this.contentBounds && (this.contentBounds.r - this.contentBounds.x); + this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y); + // if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey. // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document - Doc.UpdateDocumentExtensionForField(this.props.DataDoc || this.props.Document, this.props.fieldKey); - return ( + return !this.extensionDoc ? (null) : <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel} - style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (NumCast(this.props.Document.scrollHeight) ? NumCast(this.props.Document.scrollHeight) : "100%") : this.props.PanelHeight() }} - onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}> - <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected} - addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} setPreviewCursor={this.props.setPreviewCursor} - getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> + style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }} + onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}> + <MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} + addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> - <CollectionFreeFormLinksView {...this.props} key="freeformLinks"> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} > - {this.childViews} - </InkingCanvas> - </CollectionFreeFormLinksView> - <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> + {this.children} </CollectionFreeFormViewPannableContents> </MarqueeView> {this.overlayViews} - <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} /> - </div> - ); - } -} - -@observer -class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { - render() { - return <DocumentContentsView {...this.props} layoutKey={"overlayLayout"} - renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />; - } -} - -@observer -class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> { - render() { - return !this.props.Document.backgroundLayout ? (null) : - (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"} - renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />); + </div>; } } @@ -746,6 +889,7 @@ interface CollectionFreeFormViewPannableContentsProps { panY: () => number; zoomScaling: () => number; easing: () => boolean; + children: () => JSX.Element[]; } @observer @@ -758,7 +902,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF const pany = -this.props.panY(); const zoom = this.props.zoomScaling(); return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}> - {this.props.children} + {this.props.children()} </div>; } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx new file mode 100644 index 000000000..91fcad4be --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -0,0 +1,46 @@ +import React = require("react") +import AntimodeMenu from "../../AntimodeMenu"; +import { observer } from "mobx-react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { unimplementedFunction } from "../../../../Utils"; + +@observer +export default class MarqueeOptionsMenu extends AntimodeMenu { + static Instance: MarqueeOptionsMenu; + + public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public showMarquee: () => void = unimplementedFunction; + public hideMarquee: () => void = unimplementedFunction; + + constructor(props: Readonly<{}>) { + super(props); + + MarqueeOptionsMenu.Instance = this; + } + + render() { + let buttons = [ + <button + className="antimodeMenu-button" + title="Create a Collection" + onPointerDown={this.createCollection}> + <FontAwesomeIcon icon="object-group" size="lg" /> + </button>, + <button + className="antimodeMenu-button" + title="Summarize Documents" + onPointerDown={this.summarize}> + <FontAwesomeIcon icon="compress-arrows-alt" size="lg" /> + </button>, + <button + className="antimodeMenu-button" + title="Delete Documents" + onPointerDown={this.delete}> + <FontAwesomeIcon icon="trash-alt" size="lg" /> + </button>, + ] + return this.getElement(buttons); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 04f6ec2ad..d14495626 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -8,6 +8,7 @@ } .marqueeView { overflow: hidden; + pointer-events: inherit; } .marqueeView:focus-within { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ecdd02b0f..1066f4f8d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,7 +1,7 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../../new_fields/Doc"; -import { InkField, StrokeData } from "../../../../new_fields/InkField"; +import { InkField, PointData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { listSpec } from "../../../../new_fields/Schema"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; @@ -13,29 +13,31 @@ import { Docs } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; -import { InkingCanvas } from "../../InkingCanvas"; import { PreviewCursor } from "../../PreviewCursor"; -import { CollectionViewType } from "../CollectionBaseView"; +import { CollectionViewType } from "../CollectionView"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; import React = require("react"); +import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; +import InkSelectDecorations from "../../InkSelectDecorations"; +import { SubCollectionViewProps } from "../CollectionSubView"; interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - container: CollectionFreeFormView; addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; - selectDocuments: (docs: Doc[]) => void; + selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void; removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; - isAnnotationOverlay: boolean; + extensionDoc: Doc; + isAnnotationOverlay?: boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @observer -export class MarqueeView extends React.Component<MarqueeViewProps> +export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { private _mainCont = React.createRef<HTMLDivElement>(); @observable _lastX: number = 0; @@ -50,13 +52,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } @action - cleanupInteractions = (all: boolean = false) => { + cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => { if (all) { document.removeEventListener("pointerup", this.onPointerUp, true); document.removeEventListener("pointermove", this.onPointerMove, true); } document.removeEventListener("keydown", this.marqueeCommand, true); - this._visible = false; + if (hideMarquee) { + this._visible = false; + } } @undoBatch @@ -187,15 +191,33 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); + if (!this.props.active()) this.props.selectDocuments([this.props.Document], []); if (this._visible) { let mselect = this.marqueeSelect(); if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map(); + // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : []; + let docs = mselect.length ? mselect : [this.props.Document]; + this.props.selectDocuments(docs, []); } - this.cleanupInteractions(true); + if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { + MarqueeOptionsMenu.Instance.createCollection = this.collection; + MarqueeOptionsMenu.Instance.delete = this.delete; + MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; + MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; + MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); + } + this.cleanupInteractions(true, this._commandExecuted); + + let hideMarquee = () => { + this.hideMarquee(); + MarqueeOptionsMenu.Instance.fadeOut(true); + document.removeEventListener("pointerdown", hideMarquee); + }; + document.addEventListener("pointerdown", hideMarquee); if (e.altKey) { e.preventDefault(); @@ -245,14 +267,130 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } + get inkDoc() { + return this.props.extensionDoc; + } + get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored. - let cprops = this.props.container.props; - return Cast(Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink, InkField); + return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let cprops = this.props.container.props; - Doc.fieldExtensionDoc(cprops.Document, cprops.fieldKey).ink = value; + this.props.extensionDoc && (this.props.extensionDoc.ink = value); + } + + @action + showMarquee = () => { + this._visible = true; + } + + @action + hideMarquee = () => { + this._visible = false; + } + + @action + delete = () => { + this.marqueeSelect(false).map(d => this.props.removeDocument(d)); + if (this.ink) { + // this.marqueeInkDelete(this.ink.inkData); + } + SelectionManager.DeselectAll(); + this.cleanupInteractions(false); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + } + + getCollection = (selected: Doc[]) => { + let bounds = this.Bounds; + let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", + "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; + let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string")); + if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette); + let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]); + let usedPaletted = new Map<string, number>(); + [...this.props.activeDocuments(), this.props.Document].map(child => { + let bg = StrCast(Doc.Layout(child).backgroundColor); + if (palette.indexOf(bg) !== -1) { + palette.splice(palette.indexOf(bg), 1); + if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); + else usedPaletted.set(bg, 1); + } + }); + usedPaletted.delete("#f1efeb"); + usedPaletted.delete("white"); + usedPaletted.delete("rgba(255,255,255,1)"); + let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); + let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; + let inkData = this.ink ? this.ink.inkData : undefined; + let newCollection = Docs.Create.FreeformDocument(selected, { + x: bounds.left, + y: bounds.top, + panX: 0, + panY: 0, + backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, + defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, + width: bounds.width, + height: bounds.height, + title: "a nested collection", + }); + let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); + // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; + // this.marqueeInkDelete(inkData); + this.hideMarquee(); + return newCollection; + } + + @action + collection = (e: KeyboardEvent | React.PointerEvent | undefined) => { + let bounds = this.Bounds; + let selected = this.marqueeSelect(false); + if (e instanceof KeyboardEvent ? e.key === "c" : true) { + selected.map(d => { + this.props.removeDocument(d); + d.x = NumCast(d.x) - bounds.left - bounds.width / 2; + d.y = NumCast(d.y) - bounds.top - bounds.height / 2; + d.displayTimecode = undefined; + return d; + }); + } + let newCollection = this.getCollection(selected); + this.props.addDocument(newCollection); + this.props.selectDocuments([newCollection], []); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + } + + @action + summary = (e: KeyboardEvent | React.PointerEvent | undefined) => { + let bounds = this.Bounds; + let selected = this.marqueeSelect(false); + let newCollection = this.getCollection(selected); + + selected.map(d => { + this.props.removeDocument(d); + d.x = NumCast(d.x) - bounds.left - bounds.width / 2; + d.y = NumCast(d.y) - bounds.top - bounds.height / 2; + d.page = -1; + return d; + }); + newCollection.chromeStatus = "disabled"; + let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); + Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); + newCollection.x = bounds.left + bounds.width; + Doc.GetProto(newCollection).summaryDoc = summary; + Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); + if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); + container.viewType = CollectionViewType.Stacking; + container.autoHeight = true; + Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" + this.props.addLiveTextDocument(container); + } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them + Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" + this.props.addLiveTextDocument(summary); + } + MarqueeOptionsMenu.Instance.fadeOut(true); } @undoBatch @@ -265,12 +403,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; - this.marqueeSelect(false).map(d => this.props.removeDocument(d)); - if (this.ink) { - this.marqueeInkDelete(this.ink.inkData); - } - SelectionManager.DeselectAll(); - this.cleanupInteractions(false); + this.delete(); e.stopPropagation(); } if (e.key === "c" || e.key === "s" || e.key === "S") { @@ -278,136 +411,76 @@ export class MarqueeView extends React.Component<MarqueeViewProps> e.stopPropagation(); e.preventDefault(); (e as any).propagationIsStopped = true; - let bounds = this.Bounds; - let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.displayTimecode = undefined; - return d; - }); + this.collection(e); } - let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)", - "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",]; - let colorPalette = Cast(this.props.container.props.Document.colorPalette, listSpec("string")); - if (!colorPalette) this.props.container.props.Document.colorPalette = new List<string>(defaultPalette); - let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); - let usedPaletted = new Map<string, number>(); - [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); - if (palette.indexOf(bg) !== -1) { - palette.splice(palette.indexOf(bg), 1); - if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); - else usedPaletted.set(bg, 1); - } - }); - usedPaletted.delete("#f1efeb"); - usedPaletted.delete("white"); - usedPaletted.delete("rgba(255,255,255,1)"); - let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0); - let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0]; - let inkData = this.ink ? this.ink.inkData : undefined; - let newCollection = Docs.Create.FreeformDocument(selected, { - x: bounds.left, - y: bounds.top, - panX: 0, - panY: 0, - backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, - defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor, - width: bounds.width, - height: bounds.height, - title: "a nested collection", - }); - let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); - dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; - this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => { - this.props.removeDocument(d); - d.x = NumCast(d.x) - bounds.left - bounds.width / 2; - d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; - return d; - }); - newCollection.chromeStatus = "disabled"; - let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); - Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]); - newCollection.x = bounds.left + bounds.width; - Doc.GetProto(newCollection).summaryDoc = summary; - Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`); - if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view. - let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); - container.viewType = CollectionViewType.Stacking; - container.autoHeight = true; - Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight" - this.props.addLiveTextDocument(container); - } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them - Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight" - this.props.addLiveTextDocument(summary); - } - } - else { - this.props.addDocument(newCollection); - this.props.selectDocuments([newCollection]); + this.summary(e); } this.cleanupInteractions(false); } } - @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: StrokeData, key: string, map: any) => { - if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) { - idata.set(key, - { - pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })), - color: value.color, - width: value.width, - tool: value.tool, - page: -1 - }); - } - }); - return idata; - } + // @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)); + // @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: StrokeData, key: string, map: any) => - !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value)); - this.ink = new InkField(idata); - } - } + // 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); + // } + // } marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { + let layoutDoc = Doc.Layout(doc); var x = NumCast(doc.x); var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { + let layoutDoc = Doc.Layout(doc); var x = NumCast(doc.x); var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } @@ -420,10 +493,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps> let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let 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 => { + let layoutDoc = Doc.Layout(doc); var x = NumCast(doc.x); var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { selection.push(doc); } @@ -434,21 +508,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @computed get marqueeDiv() { + let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); - return <div className="marquee" style={{ width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} > - <span className="marquee-legend" /> + /** + * @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. + */ + 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" /> */} </div>; } render() { - let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; - return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}> - <div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} onScroll={(e) => e.currentTarget.scrollLeft = 0} > - {this._visible ? this.marqueeDiv : null} - <div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} > - {this.props.children} - </div> - </div> + return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} </div>; } }
\ No newline at end of file |
