diff options
| author | Stanley Yip <stanley_yip@brown.edu> | 2020-01-08 13:47:29 -0500 |
|---|---|---|
| committer | Stanley Yip <stanley_yip@brown.edu> | 2020-01-08 13:47:29 -0500 |
| commit | abfa42b6f2cf863deee19aac19328a23687464cb (patch) | |
| tree | b481f23ffa7bccbde7a31de34f50d765b6b73162 /src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | |
| parent | d8fc218f3481728f221ceacc60ac4bc553f8e295 (diff) | |
| parent | 19a71cb2788b9c1c8d8ced4af285bf91033ba626 (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into pen
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 341 |
1 files changed, 185 insertions, 156 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 75690ab2c..eb5a074bb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,12 +1,12 @@ 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, trace, ObservableMap, untracked, reaction, runInAction, IReactionDisposer } from "mobx"; +import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../../new_fields/Doc"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkTool } from "../../../../new_fields/InkField"; +import { InkTool, InkField, InkData } 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"; @@ -26,7 +26,7 @@ import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss" import { ContextMenu } from "../../ContextMenu"; import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingControl } from "../../InkingControl"; -import { CreatePolyline, InkingStroke } from "../../InkingStroke"; +import { CreatePolyline } from "../../InkingStroke"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentViewProps } from "../../nodes/DocumentView"; import { FormattedTextBox } from "../../nodes/FormattedTextBox"; @@ -39,10 +39,11 @@ import "./CollectionFreeFormView.scss"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { computedFn, keepAlive } from "mobx-utils"; +import { computedFn } from "mobx-utils"; import { TraceMobx } from "../../../../new_fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; import { LinkManager } from "../../../util/LinkManager"; +import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -100,10 +101,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { 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 - let maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); + const maxHeading = this.childDocs.reduce((maxHeading, doc) => NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading, 0); let heading = maxHeading === 0 || this.childDocs.length === 0 ? 1 : maxHeading === 1 ? 2 : 0; if (heading === 0) { - let sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : + const sorted = this.childDocs.filter(d => d.type === DocumentType.TEXT && d.data_ext instanceof Doc && d.data_ext.lastModified).sort((a, b) => DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date > DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? 1 : DateCast((Cast(a.data_ext, Doc) as Doc).lastModified).date < DateCast((Cast(b.data_ext, Doc) as Doc).lastModified).date ? -1 : 0); heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } @@ -111,7 +112,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.addDocument(newBox); } private addDocument = (newBox: Doc) => { - let added = this.props.addDocument(newBox); + const added = this.props.addDocument(newBox); added && this.bringToFront(newBox); added && this.updateCluster(newBox); return added; @@ -128,54 +129,54 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onDrop = (e: React.DragEvent): Promise<void> => { - var pt = this.getTransform().transformPoint(e.pageX, e.pageY); + const pt = this.getTransform().transformPoint(e.pageX, e.pageY); return super.onDrop(e, { x: pt[0], y: pt[1] }); } @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - let xf = this.getTransform(); - let xfo = this.getTransformOverlay(); - let [xp, yp] = xf.transformPoint(de.x, de.y); - let [xpo, ypo] = xfo.transformPoint(de.x, de.y); + const xf = this.getTransform(); + const xfo = this.getTransformOverlay(); + const [xp, yp] = xf.transformPoint(de.x, de.y); + const [xpo, ypo] = xfo.transformPoint(de.x, de.y); if (super.drop(e, de)) { - if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.droppedDocuments.length) { - 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(firstDoc.x); - let dropY = NumCast(firstDoc.y); - de.data.droppedDocuments.forEach(action((d: Doc) => { - let layoutDoc = Doc.Layout(d); + if (de.complete.docDragData) { + if (de.complete.docDragData.droppedDocuments.length) { + const firstDoc = de.complete.docDragData.droppedDocuments[0]; + const z = NumCast(firstDoc.z); + const x = (z ? xpo : xp) - de.complete.docDragData.offset[0]; + const y = (z ? ypo : yp) - de.complete.docDragData.offset[1]; + const dropX = NumCast(firstDoc.x); + const dropY = NumCast(firstDoc.y); + de.complete.docDragData.droppedDocuments.forEach(action((d: Doc) => { + const layoutDoc = Doc.Layout(d); d.x = x + NumCast(d.x) - dropX; d.y = y + NumCast(d.y) - dropY; if (!NumCast(layoutDoc.width)) { layoutDoc.width = 300; } if (!NumCast(layoutDoc.height)) { - let nw = NumCast(layoutDoc.nativeWidth); - let nh = NumCast(layoutDoc.nativeHeight); + const nw = NumCast(layoutDoc.nativeWidth); + const nh = NumCast(layoutDoc.nativeHeight); layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); })); - de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); + de.complete.docDragData.droppedDocuments.length === 1 && this.updateCluster(de.complete.docDragData.droppedDocuments[0]); } } - else if (de.data instanceof DragManager.AnnotationDragData) { - if (de.data.dropDocument) { - let dragDoc = de.data.dropDocument; - let x = xp - de.data.offset[0]; - let y = yp - de.data.offset[1]; - let dropX = NumCast(dragDoc.x); - let dropY = NumCast(dragDoc.y); + else if (de.complete.annoDragData) { + if (de.complete.annoDragData.dropDocument) { + const dragDoc = de.complete.annoDragData.dropDocument; + const x = xp - de.complete.annoDragData.offset[0]; + const y = yp - de.complete.annoDragData.offset[1]; + const dropX = NumCast(dragDoc.x); + const dropY = NumCast(dragDoc.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; dragDoc.y = y + NumCast(dragDoc.y) - dropY; - 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 + de.complete.annoDragData.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); } } @@ -185,31 +186,28 @@ 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(layoutDoc.width) + 2 * this._clusterDistance; - let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; + 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; }, -1); } tryDragCluster(e: PointerEvent | TouchEvent) { - let ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0); + const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0); if (ptsParent) { - let cluster = this.pickCluster(this.getTransform().transformPoint(ptsParent.clientX, ptsParent.clientY)); + const 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); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === 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); 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 - }); + DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction }); return true; } } @@ -227,10 +225,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @undoBatch @action updateCluster(doc: Doc) { - let childLayouts = this.childLayoutPairs.map(pair => pair.layout); + 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)); - let preferredInd = NumCast(doc.cluster); + const preferredInd = NumCast(doc.cluster); doc.cluster = -1; this._clusterSets.map((set, i) => set.map(member => { if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { @@ -257,15 +255,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getClusterColor = (doc: Doc) => { let clusterColor = ""; - let cluster = NumCast(doc.cluster); + const cluster = NumCast(doc.cluster); if (this.Document.useClusters) { if (this._clusterSets.length <= cluster) { setTimeout(() => this.updateCluster(doc), 0); } else { // choose a cluster color from a palette - let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; + const colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; clusterColor = colors[cluster % colors.length]; - let set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)); + const set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor)); // 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)); @@ -289,7 +287,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { e.stopPropagation(); e.preventDefault(); - let point = this.getTransform().transformPoint(e.pageX, e.pageY); + const point = this.getTransform().transformPoint(e.pageX, e.pageY); this._points.push({ X: point[0], Y: point[1] }); } // if not using a pen and in no ink mode @@ -326,8 +324,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action handle1PointerDown = (e: React.TouchEvent) => { - if (e.nativeEvent.cancelBubble) return; - let pt = e.targetTouches.item(0); + const pt = e.targetTouches.item(0); if (pt) { this._hitCluster = this.props.Document.useCluster ? this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)) !== -1 : false; if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { @@ -338,12 +335,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen) { e.stopPropagation(); e.preventDefault(); - let point = this.getTransform().transformPoint(pt.pageX, pt.pageY); + const point = this.getTransform().transformPoint(pt.pageX, pt.pageY); this._points.push({ X: point[0], Y: point[1] }); } else if (InkingControl.Instance.selectedTool === InkTool.None) { this._lastX = pt.pageX; this._lastY = pt.pageY; + e.stopPropagation(); + e.preventDefault(); } else { e.stopPropagation(); @@ -358,21 +357,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && 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 })); + const B = this.svgBounds; + const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - let result = GestureUtils.GestureRecognizer.Recognize(new Array(points)); + const result = GestureUtils.GestureRecognizer.Recognize(new Array(points)); let actionPerformed = false; if (result && result.Score > 0.7) { switch (result.Name) { case GestureUtils.Gestures.Box: - let bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.y)), b: Math.max(...this._points.map(p => p.Y)) }; - let sel = this.getActiveDocuments().filter(doc => { - let l = NumCast(doc.x); - let r = l + doc[WidthSym](); - let t = NumCast(doc.y); - let b = t + doc[HeightSym](); - let pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); + const bounds = { x: Math.min(...this._points.map(p => p.X)), r: Math.max(...this._points.map(p => p.X)), y: Math.min(...this._points.map(p => p.Y)), b: Math.max(...this._points.map(p => p.Y)) }; + const sel = this.getActiveDocuments().filter(doc => { + const l = NumCast(doc.x); + const r = l + doc[WidthSym](); + const t = NumCast(doc.y); + const b = t + doc[HeightSym](); + const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); if (pass) { doc.x = l - B.left - B.width / 2; doc.y = t - B.top - B.height / 2; @@ -384,15 +383,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { actionPerformed = true; break; case GestureUtils.Gestures.Line: - let ep1 = this._points[0]; - let ep2 = this._points[this._points.length - 1]; + const ep1 = this._points[0]; + const ep2 = this._points[this._points.length - 1]; let d1: Doc | undefined; let d2: Doc | undefined; this.getActiveDocuments().map(doc => { - let l = NumCast(doc.x); - let r = l + doc[WidthSym](); - let t = NumCast(doc.y); - let b = t + doc[HeightSym](); + const l = NumCast(doc.x); + const r = l + doc[WidthSym](); + const t = NumCast(doc.y); + const b = t + doc[HeightSym](); if (!d1 && l < ep1.X && r > ep1.X && t < ep1.Y && b > ep1.Y) { d1 = doc; } @@ -414,7 +413,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } if (!actionPerformed) { - 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 }); + const 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 = []; } @@ -433,26 +432,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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); + const docs = this.childLayoutPairs.map(pair => pair.layout); + const [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); + const minx = docs.length ? NumCast(docs[0].x) : 0; + const maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + const miny = docs.length ? NumCast(docs[0].y) : 0; + const maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + const ranges = docs.filter(doc => doc).reduce((range, doc) => { + const layoutDoc = Doc.Layout(doc); + const x = NumCast(doc.x); + const xe = x + NumCast(layoutDoc.width); + const y = NumCast(doc.y); + const 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 cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; - let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale, + const cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1; + const 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; @@ -475,7 +474,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (!e.cancelBubble) { const selectedTool = InkingControl.Instance.selectedTool; if (selectedTool === InkTool.Highlighter || selectedTool === InkTool.Pen || InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - let point = this.getTransform().transformPoint(e.clientX, e.clientY); + const point = this.getTransform().transformPoint(e.clientX, e.clientY); this._points.push({ X: point[0], Y: point[1] }); } else if (selectedTool === InkTool.None) { @@ -496,8 +495,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { handle1PointerMove = (e: TouchEvent) => { // panning a workspace if (!e.cancelBubble) { - let myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); - let pt = myTouches[0]; + const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); + const pt = myTouches[0]; if (pt) { if (InkingControl.Instance.selectedTool === InkTool.None) { if (this._hitCluster && this.tryDragCluster(e)) { @@ -510,7 +509,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.pan(pt); } else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) { - let point = this.getTransform().transformPoint(pt.clientX, pt.clientY); + const point = this.getTransform().transformPoint(pt.clientX, pt.clientY); this._points.push({ X: point[0], Y: point[1] }); } } @@ -522,28 +521,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { handle2PointersMove = (e: TouchEvent) => { // pinch zooming if (!e.cancelBubble) { - let myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); - let pt1 = myTouches[0]; - let pt2 = myTouches[1]; + const myTouches = InteractionUtils.GetMyTargetTouches(e, this.prevPoints); + const pt1 = myTouches[0]; + const pt2 = myTouches[1]; if (this.prevPoints.size === 2) { - let oldPoint1 = this.prevPoints.get(pt1.identifier); - let oldPoint2 = this.prevPoints.get(pt2.identifier); + const oldPoint1 = this.prevPoints.get(pt1.identifier); + const oldPoint2 = this.prevPoints.get(pt2.identifier); if (oldPoint1 && oldPoint2) { - let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2); + const 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; + const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2)); + const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2)); + const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; // calculate the raw delta value - let rawDelta = (dir * (d1 + d2)); + const rawDelta = (dir * (d1 + d2)); // this floors and ceils the delta value to prevent jitteriness - let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8); + const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8); this.zoom(centerX, centerY, delta * window.devicePixelRatio); this.prevPoints.set(pt1.identifier, pt1); this.prevPoints.set(pt2.identifier, pt2); @@ -551,27 +550,28 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // 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; + const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + const 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(); } - e.stopPropagation(); - e.preventDefault(); } + @action handle2PointersDown = (e: React.TouchEvent) => { if (!e.nativeEvent.cancelBubble && this.props.active(true)) { - let pt1: React.Touch | null = e.targetTouches.item(0); - let pt2: React.Touch | null = e.targetTouches.item(1); + const pt1: React.Touch | null = e.targetTouches.item(0); + const 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; + const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2; + const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2; this._lastX = centerX; this._lastY = centerY; document.removeEventListener("touchmove", this.onTouch); @@ -596,11 +596,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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); + const [x, y] = this.getTransform().transformPoint(pointX, pointY); + const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); if (localTransform.Scale >= 0.15) { - let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); + const 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); } @@ -622,7 +622,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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 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.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY)); this.Document.panX = this.isAnnotationOverlay ? newPanX : panX; @@ -647,6 +647,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => { const state = HistoryUtil.getState(); + // TODO This technically isn't correct if type !== "doc", as // currently nothing is done, but we should probably push a new state if (state.type === "doc" && this.Document.panX !== undefined && this.Document.panY !== undefined) { @@ -662,28 +663,29 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } SelectionManager.DeselectAll(); if (this.props.Document.scrollHeight) { - let annotOn = Cast(doc.annotationOn, Doc) as Doc; + const annotOn = Cast(doc.annotationOn, Doc) as Doc; if (!annotOn) { this.props.focus(doc); } else { - let contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height); - let offset = annotOn && (contextHgt / 2 * 96 / 72); + const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height); + const offset = annotOn && (contextHgt / 2 * 96 / 72); this.props.Document.scrollY = NumCast(doc.y) - offset; } } else { - let layoutdoc = Doc.Layout(doc); + const 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 }; + const savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType }; - this.setPan(newPanX, newPanY, "Ease"); + if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // 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 Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); willZoom && this.setScaleToZoom(layoutdoc, scale); + Doc.linkFollowHighlight(doc); afterFocus && setTimeout(() => { if (afterFocus && afterFocus()) { @@ -707,11 +709,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getScale = () => this.Document.scale || 1; + @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } + getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, DataDoc: childData, Document: childLayout, + LibraryPath: this.libraryPath, 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 @@ -763,7 +768,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } } - childDataProvider = computedFn(function childDataProvider(doc: Doc) { return (this as any)._layoutPoolData.get(doc[Id]); }.bind(this)); + childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) { return this._layoutPoolData.get(doc[Id]); }.bind(this)); doPivotLayout(poolData: ObservableMap<string, any>) { return computePivotLayout(poolData, this.props.Document, this.childDocs, @@ -771,10 +776,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } doFreeformLayout(poolData: ObservableMap<string, any>) { - let layoutDocs = this.childLayoutPairs.map(pair => pair.layout); + const layoutDocs = this.childLayoutPairs.map(pair => pair.layout); const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log); let state = initResult && initResult.success ? initResult.result.scriptState : undefined; - let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; + const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { const data = poolData.get(pair.layout[Id]); @@ -793,7 +798,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break; default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break; } - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => + this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair => computedElementData.elements.push({ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} dataProvider={this.childDataProvider} ruleProvider={this.Document.isRuleProvider ? this.props.Document : this.props.ruleProvider} @@ -805,6 +810,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } componentDidMount() { + super.componentDidMount(); this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; }, action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)), { fireImmediately: true, name: "doLayout" }); @@ -823,7 +829,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { layoutDocsInGrid = () => { UndoManager.RunInBatch(() => { const docs = DocListCast(this.Document[this.props.fieldKey]); - let startX = this.Document.panX || 0; + const startX = this.Document.panX || 0; let x = startX; let y = this.Document.panY || 0; let i = 0; @@ -848,8 +854,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { 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(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; + const headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); + const 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) { Doc.GetProto(this.props.Document)["ruleColor_" + NumCast(headingLayout.heading)] = headingLayout.backgroundColor; } @@ -858,17 +864,30 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } analyzeStrokes = async () => { - // CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], data.inkData); + const children = await DocListCastAsync(this.dataDoc.data); + if (!children) { + return; + } + const inkData: InkData[] = []; + for (const doc of children) { + const data = Cast(doc.data, InkField)?.inkData; + data && inkData.push(data); + } + if (!inkData.length) { + return; + } + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData); } onContextMenu = (e: React.MouseEvent) => { - let layoutItems: ContextMenuProps[] = []; + const layoutItems: ContextMenuProps[] = []; 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" }); - layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: async () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + layoutItems.push({ description: `${this.Document.LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document.LODdisable = !this.Document.LODdisable, icon: "table" }); + layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document.fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); layoutItems.push({ description: `${this.Document.isRuleProvider ? "Stop Auto Format" : "Auto Format"}`, event: this.autoFormat, icon: "chalkboard" }); layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); @@ -881,7 +900,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { input.accept = ".zip"; input.onchange = async _e => { const upload = Utils.prepend("/uploadDoc"); - let formData = new FormData(); + const formData = new FormData(); const file = input.files && input.files[0]; if (file) { formData.append('file', file); @@ -916,7 +935,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private childViews = () => { - let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; + const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; return [ ...children, ...this.views, @@ -924,12 +943,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @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); + const xs = this._points.map(p => p.X); + const ys = this._points.map(p => p.Y); + const right = Math.max(...xs); + const left = Math.min(...xs); + const bottom = Math.max(...ys); + const top = Math.min(...ys); return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top }; } @@ -938,7 +957,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return (null); } - let B = this.svgBounds; + const B = this.svgBounds; return ( <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, position: "absolute", zIndex: 30000 }}> @@ -948,12 +967,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } children = () => { - let eles: JSX.Element[] = []; + const eles: JSX.Element[] = []; this.extensionDoc && (eles.push(...this.childViews())); this.currentStroke && (eles.push(this.currentStroke)); 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}</span> + </div>; + } + @computed get marqueeView() { + return <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}> + {this.children} + </CollectionFreeFormViewPannableContents> + </MarqueeView>; + } render() { TraceMobx(); // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) @@ -963,19 +996,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // 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 - return !this.extensionDoc ? (null) : - <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, - style={{ 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}> - {this.children} - </CollectionFreeFormViewPannableContents> - </MarqueeView> - <CollectionFreeFormOverlayView elements={this.elementFunc} /> - </div>; + if (!this.extensionDoc) return (null); + // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale; + return <div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, + 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}> + {!this.Document.LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ? + this.placeholder : this.marqueeView} + <CollectionFreeFormOverlayView elements={this.elementFunc} /> + </div>; } } @@ -1003,7 +1032,7 @@ interface CollectionFreeFormViewPannableContentsProps { @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ render() { - let freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none"); + const freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none"); const cenx = this.props.centeringShiftX(); const ceny = this.props.centeringShiftY(); const panx = -this.props.panX(); |
