diff options
| author | anika-ahluwalia <anika.ahluwalia@gmail.com> | 2020-05-15 13:26:37 -0500 |
|---|---|---|
| committer | anika-ahluwalia <anika.ahluwalia@gmail.com> | 2020-05-15 13:26:37 -0500 |
| commit | 4bca98333ada6536a1bf2ecf1681c5c17a3a1ae1 (patch) | |
| tree | 7fc20099971de42756af3d238e2ea4f9a608cbd3 /src/client/views/collections/collectionFreeForm | |
| parent | 0f54ef61653213bd1b26300cb7d14e3da71d1eea (diff) | |
| parent | 98c7540fff67c232c1b04f2130ee624f9a70afbd (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into script_documents
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
7 files changed, 310 insertions, 149 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9a864078a..3860ce2d7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,15 +1,15 @@ -import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../../new_fields/Types"; +import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../fields/Doc"; +import { NumCast, StrCast, Cast } from "../../../../fields/Types"; import { ScriptBox } from "../../ScriptBox"; import { CompileScript } from "../../../util/Scripting"; -import { ScriptField } from "../../../../new_fields/ScriptField"; +import { ScriptField } from "../../../../fields/ScriptField"; import { OverlayView, OverlayElementOptions } from "../../OverlayView"; import { emptyFunction, aggregateBounds } from "../../../../Utils"; import React = require("react"); -import { Id, ToString } from "../../../../new_fields/FieldSymbols"; -import { ObjectField } from "../../../../new_fields/ObjectField"; -import { RefField } from "../../../../new_fields/RefField"; -import { listSpec } from "../../../../new_fields/Schema"; +import { Id, ToString } from "../../../../fields/FieldSymbols"; +import { ObjectField } from "../../../../fields/ObjectField"; +import { RefField } from "../../../../fields/RefField"; +import { listSpec } from "../../../../fields/Schema"; export interface ViewDefBounds { type: string; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index cf12ef382..f3fc04752 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { Doc } from "../../../../new_fields/Doc"; +import { Doc } from "../../../../fields/Doc"; import { Utils } from '../../../../Utils'; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; @@ -7,8 +7,9 @@ import React = require("react"); import v5 = require("uuid/v5"); import { DocumentType } from "../../../documents/DocumentTypes"; import { observable, action, reaction, IReactionDisposer } from "mobx"; -import { StrCast } from "../../../../new_fields/Types"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { StrCast, Cast } from "../../../../fields/Types"; +import { Id } from "../../../../fields/FieldSymbols"; +import { SnappingManager } from "../../../util/SnappingManager"; export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -24,6 +25,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo componentDidMount() { this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)], action(() => { + if (SnappingManager.GetIsDragging()) return; setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; @@ -40,7 +42,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const bpt = Utils.closestPtBetweenRectangles(bbounds.left, bbounds.top, bbounds.width, bbounds.height, abounds.left, abounds.top, abounds.width, abounds.height, apt.point.x, apt.point.y); - const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1"; + const afield = this.props.A.props.LayoutTemplateString?.indexOf("anchor1") === -1 ? "anchor2" : "anchor1"; const bfield = afield === "anchor1" ? "anchor2" : "anchor1"; // really hacky stuff to make the LinkAnchorBox display where we want it to: @@ -79,11 +81,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo componentWillUnmount() { this._anchorDisposer?.(); } - render() { + if (SnappingManager.GetIsDragging()) return null; this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform()); - const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; - const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : []; + const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont"); + const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont"); const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect(); const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect(); const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height, @@ -105,15 +107,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document); const text = StrCast(this.props.A.props.Document.linkRelationship); return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}> + <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}> {text !== "-ungrouped-" ? text : ""} </text> <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} /> - {/* <line key="linkLine" className="collectionfreeformlinkview-linkLine" - style={{ opacity: this._opacity, strokeDasharray: "2 2" }} - 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.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 4b5e977df..ae81b4b36 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,15 +1,15 @@ import { computed } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../../../new_fields/Doc"; -import { Id } from "../../../../new_fields/FieldSymbols"; +import { Doc } from "../../../../fields/Doc"; +import { Id } from "../../../../fields/FieldSymbols"; import { DocumentManager } from "../../../util/DocumentManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); import { Utils, emptyFunction } from "../../../../Utils"; -import { SelectionManager } from "../../../util/SelectionManager"; import { DocumentType } from "../../../documents/DocumentTypes"; +import { SnappingManager } from "../../../util/SnappingManager"; @observer export class CollectionFreeFormLinksView extends React.Component { @@ -30,13 +30,13 @@ export class CollectionFreeFormLinksView extends React.Component { return drawnPairs; }, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]); return connections.filter(c => - c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK && - c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed + c.a.props.Document.type === DocumentType.LINK && + c.a.props.pinToPres !== emptyFunction && c.b.props.pinToPres !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />); } render() { - return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container"> + return SnappingManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container"> <svg className="collectionfreeformlinksview-svgCanvas"> {this.uniqueConnections} </svg> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 92fa2781c..548ad78a5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,16 +1,16 @@ import { observer } from "mobx-react"; import * as mobxUtils from 'mobx-utils'; -import CursorField from "../../../../new_fields/CursorField"; -import { listSpec } from "../../../../new_fields/Schema"; -import { Cast } from "../../../../new_fields/Types"; -import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils"; +import CursorField from "../../../../fields/CursorField"; +import { listSpec } from "../../../../fields/Schema"; +import { Cast } from "../../../../fields/Types"; +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { CollectionViewProps } from "../CollectionSubView"; import "./CollectionFreeFormView.scss"; import React = require("react"); import v5 = require("uuid/v5"); import { computed } from "mobx"; -import { FieldResult } from "../../../../new_fields/Doc"; -import { List } from "../../../../new_fields/List"; +import { FieldResult } from "../../../../fields/Doc"; +import { List } from "../../../../fields/List"; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d85233041..bf679309c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,21 +1,21 @@ import { library } from "@fortawesome/fontawesome-svg-core"; -import { faEye } from "@fortawesome/free-regular-svg-icons"; +import { faEye, faEyeSlash } 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, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, _allowStateChangesInsideComputed } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc"; -import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; -import { Id } from "../../../../new_fields/FieldSymbols"; -import { InkData, InkField, InkTool } from "../../../../new_fields/InkField"; -import { List } from "../../../../new_fields/List"; -import { RichTextField } from "../../../../new_fields/RichTextField"; -import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema"; -import { ScriptField } from "../../../../new_fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types"; -import { TraceMobx } from "../../../../new_fields/util"; +import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../fields/Doc"; +import { documentSchema, collectionSchema } from "../../../../fields/documentSchemas"; +import { Id } from "../../../../fields/FieldSymbols"; +import { InkData, InkField, InkTool, PointData } from "../../../../fields/InkField"; +import { List } from "../../../../fields/List"; +import { RichTextField } from "../../../../fields/RichTextField"; +import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema"; +import { ScriptField, ComputedField } from "../../../../fields/ScriptField"; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse, numberRange } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs } from "../../../documents/Documents"; @@ -45,6 +45,7 @@ import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { CollectionViewType } from "../CollectionView"; import { Timeline } from "../../animationtimeline/Timeline"; +import { SnappingManager } from "../../../util/SnappingManager"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -66,11 +67,10 @@ export const panZoomSchema = createSchema({ fitH: "number" }); -type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>; -const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema); +type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>; +const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema); export type collectionFreeformViewProps = { forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox) - childClickScript?: ScriptField; viewDefDivClick?: ScriptField; }; @@ -113,8 +113,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) : this.Document.scale || 1) - private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections - private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections + private centeringShiftX = () => !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling / this.contentScaling : 0; // shift so pan position is at center of window for non-overlay collections + private centeringShiftY = () => !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling / this.contentScaling : 0;// shift so pan position is at center of window for non-overlay collections private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform()); private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); @@ -123,11 +123,27 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P FormattedTextBox.SelectOnLoad = newBox[Id];// track the new text box so we can give it a prop that tells it to focus itself when it's displayed this.addDocument(newBox); } - private addDocument = (newBox: Doc) => { - const added = this.props.addDocument(newBox); - added && this.bringToFront(newBox); - added && this.updateCluster(newBox); - return added; + addDocument = (newBox: Doc | Doc[]) => { + const timecode = Cast(this.props.Document.timecode, "number", null); + if (timecode !== undefined) { + ((newBox instanceof Doc) ? [newBox] : newBox).map(doc => { + doc["x-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.x))); + doc["y-indexed"] = new List<number>(numberRange(timecode + 1).map(i => NumCast(doc.y))); + doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document }); + doc.x = ComputedField.MakeInterpolated("x", "timecode"); + doc.y = ComputedField.MakeInterpolated("y", "timecode"); + }); + } + + if (newBox instanceof Doc) { + const added = this.props.addDocument(newBox); + added && this.bringToFront(newBox); + added && this.updateCluster(newBox); + return added; + } else { + return this.props.addDocument(newBox); + // bcz: deal with clusters + } } private selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); @@ -153,6 +169,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const xfo = this.getTransformOverlay(); const [xp, yp] = xf.transformPoint(de.x, de.y); const [xpo, ypo] = xfo.transformPoint(de.x, de.y); + const zsorted = this.childLayoutPairs.map(pair => pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); if (super.onInternalDrop(e, de)) { if (de.complete.docDragData) { if (de.complete.docDragData.droppedDocuments.length) { @@ -162,20 +179,25 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P 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)) { - const nw = NumCast(layoutDoc._nativeWidth); - const nh = NumCast(layoutDoc._nativeHeight); - layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300; + const droppedDocs = de.complete.docDragData.droppedDocuments; + runInAction(() => { + zsorted.forEach((doc, index) => doc.zIndex = index + 1); + for (let i = 0; i < droppedDocs.length; i++) { + const d = droppedDocs[i]; + 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)) { + const nw = NumCast(layoutDoc._nativeWidth); + const nh = NumCast(layoutDoc._nativeHeight); + layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300; + } + d.isBackground === undefined && (d.zIndex = zsorted.length + 1 + i); // bringToFront } - d.isBackground === undefined && this.bringToFront(d); - })); + }); (de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments); } @@ -328,6 +350,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; if (e.button === 0 && (!e.shiftKey || this._hitCluster) && !e.altKey && !e.ctrlKey && this.props.active(true)) { + + // if (!this.props.Document.aliasOf && !this.props.ContainingCollectionView) { + // this.props.addDocTab(this.props.Document, "replace"); + // e.stopPropagation(); + // e.preventDefault(); + // return; + // } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -383,7 +412,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P case GestureUtils.Gestures.Stroke: const points = ge.points; const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); - const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height }); + const inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, InkingControl.Instance.selectedWidth, points, { title: "ink stroke", x: B.x, y: B.y, _width: B.width, _height: B.height }); this.addDocument(inkDoc); e.stopPropagation(); break; @@ -552,7 +581,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P document.removeEventListener("pointerup", this.onPointerUp); return; } - this.pan(e); + (!MarqueeView.DragMarquee || e.altKey) && this.pan(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(); @@ -698,7 +727,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action zoom = (pointX: number, pointY: number, deltaY: number): void => { - let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1; + let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); } @@ -706,7 +735,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const [x, y] = this.getTransform().transformPoint(pointX, pointY); const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y); - if (localTransform.Scale >= 0.15) { + if (localTransform.Scale >= 0.15 || localTransform.Scale > this.zoomScaling()) { 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); @@ -721,7 +750,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else if (this.props.active(true)) { e.stopPropagation(); - this.zoom(e.clientX, e.clientY, e.deltaY); + if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true); + else this.zoom(e.clientX, e.clientY, e.deltaY); } this.props.Document.targetScale = NumCast(this.props.Document.scale); } @@ -760,20 +790,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - bringToFront = (doc: Doc, sendToBack?: boolean) => { + bringToFront = action((doc: Doc, sendToBack?: boolean) => { if (sendToBack || doc.isBackground) { doc.zIndex = 0; } else { const docs = this.childLayoutPairs.map(pair => pair.layout); - docs.slice().sort((doc1, doc2) => { - if (doc1 === doc) return 1; - if (doc2 === doc) return -1; - return NumCast(doc1.zIndex) - NumCast(doc2.zIndex); - }).forEach((doc, index) => doc.zIndex = index + 1); - doc.zIndex = docs.length + 1; + docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1; + if (zlast - docs.length > 100) { + for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; + zlast = docs.length + 1; + } + doc.zIndex = zlast + 1; } - } + }); scaleAtPt(docpt: number[], scale: number) { const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); @@ -855,9 +886,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); } - backgroundHalo = () => BoolCast(this.Document.useClusters); + @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); } @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); } - parentActive = () => this.props.active() || this.backgroundActive ? true : false; + backgroundHalo = () => BoolCast(this.Document.useClusters); + parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, @@ -867,12 +899,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P DataDoc: childData, Document: childLayout, LibraryPath: this.libraryPath, + LayoutTemplate: this.props.ChildLayoutTemplate, + LayoutTemplateString: this.props.ChildLayoutString, FreezeDimensions: this.props.freezeChildDimensions, layoutKey: undefined, + setupDragLines: this.setupDragLines, + dontRegisterView: this.props.dontRegisterView, rootSelected: childData ? this.rootSelected : returnFalse, dropAction: StrCast(this.props.Document.childDropAction) as dropActionType, - //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 onClick: this.onChildClickHandler, + onDoubleClick: this.onChildDoubleClickHandler, ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, PanelWidth: childLayout[WidthSym], @@ -891,14 +927,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P addDocTab = action((doc: Doc, where: string) => { if (where === "inParent") { - const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); - doc.x = pt[0]; - doc.y = pt[1]; - this.props.addDocument(doc); - return true; + if (doc instanceof Doc) { + const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); + doc.x = pt[0]; + doc.y = pt[1]; + return this.props.addDocument(doc); + } else { + (doc as any as Doc[]).forEach(doc => { + const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y)); + doc.x = pt[0]; + doc.y = pt[1]; + }); + return this.props.addDocument(doc); + } } if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { - this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); + this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List<Doc>(doc as any as Doc[]); return true; } return this.props.addDocTab(doc, where); @@ -912,6 +956,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const { x, y, z, color, zIndex } = params.pair.layout; return { x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), + transition: StrCast(layoutDoc.transition), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: "" }; } @@ -996,11 +1041,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } - childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; - runInAction(() => - Array.from(newPool.entries()).map(entry => { + const array = Array.from(newPool.entries()); + runInAction(() => { + for (const entry of array) { const lastPos = this._cachedPool.get(entry[0]); // last computed pos const newPos = entry[1]; if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) { @@ -1009,7 +1054,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) { this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height }); } - })); + } + }); this._cachedPool.clear(); Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements: ViewDefResult[] = computedElementData.slice(); @@ -1022,7 +1068,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P replica={entry[1].replica} dataProvider={this.childDataProvider} sizeProvider={this.childSizeProvider} - LayoutDoc={this.childLayoutDocFunc} pointerEvents={ this.backgroundActive ? true : @@ -1035,11 +1080,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); + if (this.props.isAnnotationOverlay) { + this.props.Document.scale = Math.max(1, NumCast(this.props.Document.scale)); + } + return elements; } + @action componentDidMount() { - super.componentDidMount(); + super.componentDidMount?.(); this._layoutComputeReaction = reaction(() => this.doLayoutComputation, (elements) => this._layoutElements = elements || [], { fireImmediately: true, name: "doLayout" }); @@ -1056,12 +1106,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } promoteCollection = undoBatch(action(() => { - this.childDocs.forEach(doc => { + const childDocs = this.childDocs.slice(); + childDocs.forEach(doc => { const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y)); doc.x = scr?.[0]; doc.y = scr?.[1]; - this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc); }); + this.props.addDocTab(childDocs as any as Doc, "inParent"); this.props.ContainingCollectionView?.removeDocument(this.props.Document); })); layoutDocsInGrid = () => { @@ -1085,23 +1136,69 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }); }, "arrange contents"); } + @undoBatch + @action + toggleNativeDimensions = () => { + Doc.toggleNativeDimensions(this.layoutDoc, this.props.ContentScaling(), this.props.NativeWidth(), this.props.NativeHeight()); + } + + @undoBatch + @action + snaphsotInterpolated = (): void => { + if (this.props.Document.timecode === undefined) { + this.childDocs.map(doc => { + this.props.Document.timecode = 0; + doc["x-indexed"] = new List<number>([NumCast(doc.x)]); + doc["y-indexed"] = new List<number>([NumCast(doc.y)]); + doc.timecode = ComputedField.MakeFunction("collection.timecode", {}, { collection: this.props.Document }); + doc.x = ComputedField.MakeInterpolated("x", "timecode"); + doc.y = ComputedField.MakeInterpolated("y", "timecode"); + }); + } + const timecode = NumCast(this.props.Document.timecode); + this.childDocs.map(doc => { + const xindexed = Cast(doc['x-indexed'], listSpec("number"), null); + const yindexed = Cast(doc['y-indexed'], listSpec("number"), null); + xindexed.length <= timecode + 1 && xindexed.push(NumCast(doc.x)); + yindexed.length <= timecode + 1 && yindexed.push(NumCast(doc.y)); + }); + this.childDocs.map(doc => doc.transition = "transform 1s"); + this.props.Document.timecode = Math.max(0, timecode + 1); + setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010); + } + @undoBatch + @action + backupInterpolated = (): void => { + this.childDocs.map(doc => doc.transition = "transform 1s"); + this.props.Document.timecode = Math.max(0, NumCast(this.props.Document.timecode) - 1); + setTimeout(() => this.childDocs.map(doc => doc.transition = undefined), 1010); + } + private thumbIdentifier?: number; onContextMenu = (e: React.MouseEvent) => { - if (this.props.children && this.props.annotationsKey) return; + if (this.props.annotationsKey) return; + + ContextMenu.Instance.addItem({ + description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => { + this._timelineVisible = !this._timelineVisible; + }), icon: this._timelineVisible ? faEyeSlash : faEye + }); + ContextMenu.Instance.addItem({ description: "Advance", event: this.snaphsotInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); + ContextMenu.Instance.addItem({ description: "Backup ", event: this.backupInterpolated, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); + const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; - this._timelineRef.current!.timelineContextMenu(e); optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); - optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); + optionItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }); this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" }); optionItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); @@ -1129,9 +1226,59 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P input.click(); } }); - ContextMenu.Instance.addItem({ description: "Options ...", subitems: optionItems, icon: "eye" }); - this._timelineRef.current!.timelineContextMenu(e); + optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); + ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); + + } + @observable _timelineVisible = false; + + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + + @action + setupDragLines = () => { + const activeDocs = this.getActiveDocuments(); + if (activeDocs.length > 50) { + DragManager.SetSnapLines([], []); + return; + } + const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); + const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; + const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); + const isDocInView = (doc: Doc, rect: { left: number, top: number, width: number, height: number }) => { + if (this.intersectRect(docDims(doc), rect)) { + snappableDocs.push(doc); + } + }; + const snappableDocs: Doc[] = []; // the set of documents in the visible viewport that we will try to snap to; + const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; + this.getActiveDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z === undefined).map(doc => isDocInView(doc, selRect)); // if not, see if there are background docs to snap to + !snappableDocs.length && this.getActiveDocuments().filter(doc => doc.z !== undefined).map(doc => isDocInView(doc, otherBounds)); // if not, then why not snap to floating docs + + const horizLines: number[] = []; + const vertLines: number[] = []; + snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { + const { left, top, width, height } = docDims(doc); + const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); + const docSize = this.getTransform().inverse().transformDirection(width, height); + + horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line + vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line + }); + DragManager.SetSnapLines(horizLines, vertLines); } + onPointerOver = (e: React.PointerEvent) => { + if (SnappingManager.GetIsDragging()) { + this.setupDragLines(); + } + e.stopPropagation(); + } + + @observable private _hLines: number[] | undefined; + @observable private _vLines: number[] | undefined; private childViews = () => { const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; @@ -1165,13 +1312,21 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return false; }); @computed get marqueeView() { - return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} - addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> + return <MarqueeView {...this.props} + nudge={this.nudge} + addDocTab={this.addDocTab} + 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} shifted={!this.nativeHeight && !this.isAnnotationOverlay} easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> {this.children} </CollectionFreeFormViewPannableContents> - <Timeline ref={this._timelineRef} {...this.props} /> + {this._timelineVisible ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)} </MarqueeView>; } @@ -1183,7 +1338,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const wscale = nw ? this.props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } - @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); } + @computed get backgroundEvents() { return this.layoutDoc.isBackground && SnappingManager.GetIsDragging(); } render() { TraceMobx(); const clientRect = this._mainCont?.getBoundingClientRect(); @@ -1196,7 +1351,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P // otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget} - onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, + onPointerOver={this.onPointerOver} + onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: DraggingManager.GetIsDragging() ? "all" : undefined, onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} style={{ pointerEvents: this.backgroundEvents ? "all" : undefined, @@ -1220,7 +1376,13 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }}> </div> - + {// uncomment to show snap lines + <div className="snapLines" style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%", pointerEvents: "none" }}> + <svg style={{ width: "100%", height: "100%" }}> + {this._hLines?.map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />)} + {this._vLines?.map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)} + </svg> + </div>} </div >; } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 1291e7dc1..a811dd15a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -7,6 +7,7 @@ height:100%; overflow: hidden; border-radius: inherit; + user-select: none; } .marqueeView:focus-within { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c70301b2f..492ba6ed6 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,37 +1,34 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc"; -import { InkField, InkData } from "../../../../new_fields/InkField"; -import { List } from "../../../../new_fields/List"; -import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; -import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types"; +import { Doc, Opt } from "../../../../fields/Doc"; +import { InkData, InkField } from "../../../../fields/InkField"; +import { List } from "../../../../fields/List"; +import { RichTextField } from "../../../../fields/RichTextField"; +import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; +import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { Utils } from "../../../../Utils"; -import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents"; +import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; +import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { ContextMenu } from "../../ContextMenu"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PreviewCursor } from "../../PreviewCursor"; import { SubCollectionViewProps } from "../CollectionSubView"; +import { CollectionView } from "../CollectionView"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { RichTextField } from "../../../../new_fields/RichTextField"; -import { CollectionView } from "../CollectionView"; -import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; -import { ScriptField } from "../../../../new_fields/ScriptField"; +import { InteractionUtils } from "../../../util/InteractionUtils"; interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; - addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void; - removeDocument: (doc: Doc) => boolean; addLiveTextDocument: (doc: Doc) => void; isSelected: () => boolean; - isAnnotationOverlay?: boolean; nudge: (x: number, y: number) => boolean; setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; } @@ -39,7 +36,7 @@ interface MarqueeViewProps { @observer export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> { - private _mainCont = React.createRef<HTMLDivElement>(); + @observable public static DragMarquee = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @observable _downX: number = 0; @@ -108,7 +105,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); } else if (!e.ctrlKey) { FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : ""; - const tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }); + const tbox = Docs.Create.TextDocument("", { + _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize), + _backgroundColor: StrCast(Doc.UserDoc().backgroundColor), + title: "-typed text-" + }); const template = FormattedTextBox.DefaultLayout; if (template instanceof Doc) { tbox._width = NumCast(template._width); @@ -164,12 +165,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.clientX; this._downY = this._lastY = e.clientY; - if (e.button === 2 || (e.button === 0 && e.altKey)) { + // allow marquee if right click OR alt+left click OR space bar + left click + if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) { + // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { this.setPreviewCursor(e.clientX, e.clientY, true); - if (e.altKey) { - //e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors. - e.preventDefault(); - } + // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors. + e.preventDefault(); + // } // bcz: do we need this? it kills the context menu on the main collection if !altKey // e.stopPropagation(); } @@ -191,14 +193,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else { this.cleanupInteractions(true); // stop listening for events if another lower-level handle (e.g. another Marquee) has stopPropagated this } - if (e.altKey) { + if (e.altKey || MarqueeView.DragMarquee) { e.preventDefault(); } } @action onPointerUp = (e: PointerEvent): void => { - if (!this.props.active(true)) this.props.selectDocuments([this.props.Document], []); if (this._visible) { const mselect = this.marqueeSelect(); if (!e.shiftKey) { @@ -209,6 +210,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const docs = mselect.length ? mselect : [this.props.Document]; this.props.selectDocuments(docs, []); } + const hideMarquee = () => { + this.hideMarquee(); + MarqueeOptionsMenu.Instance.fadeOut(true); + document.removeEventListener("pointerdown", hideMarquee); + }; if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; @@ -217,17 +223,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); + document.addEventListener("pointerdown", hideMarquee); + } else { + this.hideMarquee(); } this.cleanupInteractions(true, this._commandExecuted); - const hideMarquee = () => { - this.hideMarquee(); - MarqueeOptionsMenu.Instance.fadeOut(true); - document.removeEventListener("pointerdown", hideMarquee); - }; - document.addEventListener("pointerdown", hideMarquee); - - if (e.altKey) { + if (e.altKey || MarqueeView.DragMarquee) { e.preventDefault(); } } @@ -300,10 +302,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @action delete = () => { - this.marqueeSelect(false).map(d => this.props.removeDocument(d)); - if (this.ink) { - // this.marqueeInkDelete(this.ink.inkData); - } + this.props.removeDocument(this.marqueeSelect(false)); SelectionManager.DeselectAll(); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -349,13 +348,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const bounds = this.Bounds; const selected = this.marqueeSelect(false); if (e instanceof KeyboardEvent ? e.key === "c" : true) { - selected.map(d => { - this.props.removeDocument(d); + selected.map(action(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; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection return d; - }); + })); + this.props.removeDocument(selected); } const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined); this.props.addDocument(newCollection); @@ -551,7 +551,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque marqueeSelect(selectBackgrounds: boolean = true) { const selRect = this.Bounds; const selection: Doc[] = []; - this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { + this.props.activeDocuments().filter(doc => !doc.isBackground && !doc.z).map(doc => { const layoutDoc = Doc.Layout(doc); const x = NumCast(doc.x); const y = NumCast(doc.y); @@ -613,7 +613,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque render() { return <div className="marqueeView" - style={{ overflow: StrCast(this.props.Document.overflow), }} + style={{ overflow: StrCast(this.props.Document._overflow), cursor: MarqueeView.DragMarquee && this ? "crosshair" : "hand" }} onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}> {this._visible ? this.marqueeDiv : null} {this.props.children} |
