From da19cbc10d28b2e39a6592b80880106a415acc1c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Jul 2022 09:01:53 -0400 Subject: allow longer lines - people can always break them up explicitly if they don't look right. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 1637 +++++++++++--------- 1 file changed, 903 insertions(+), 734 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b9da4faa4..d33f0b5be 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,65 +1,64 @@ -import { Bezier } from "bezier-js"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { computedFn } from "mobx-utils"; -import { DateField } from "../../../../fields/DateField"; -import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fields/InkField"; -import { List } from "../../../../fields/List"; -import { ObjectField } from "../../../../fields/ObjectField"; -import { RichTextField } from "../../../../fields/RichTextField"; -import { listSpec } from "../../../../fields/Schema"; -import { ScriptField } from "../../../../fields/ScriptField"; -import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; -import { ImageField } from "../../../../fields/URLField"; -import { TraceMobx } from "../../../../fields/util"; -import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; -import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; -import { DocServer } from "../../../DocServer"; -import { Docs, DocUtils } from "../../../documents/Documents"; -import { DocumentType } from "../../../documents/DocumentTypes"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { DragManager, dropActionType } from "../../../util/DragManager"; -import { HistoryUtil } from "../../../util/History"; -import { InteractionUtils } from "../../../util/InteractionUtils"; -import { LinkManager } from "../../../util/LinkManager"; -import { RecordingApi } from "../../../util/RecordingApi"; -import { ScriptingGlobals } from "../../../util/ScriptingGlobals"; -import { SearchUtil } from "../../../util/SearchUtil"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { ColorScheme } from "../../../util/SettingsManager"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { Transform } from "../../../util/Transform"; -import { undoBatch, UndoManager } from "../../../util/UndoManager"; -import { COLLECTION_BORDER_WIDTH } from "../../../views/global/globalCssVariables.scss"; -import { Timeline } from "../../animationtimeline/Timeline"; -import { ContextMenu } from "../../ContextMenu"; -import { GestureOverlay } from "../../GestureOverlay"; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from "../../InkingStroke"; -import { LightboxView } from "../../LightboxView"; -import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; -import { FieldViewProps } from "../../nodes/FieldView"; -import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; -import { PresBox } from "../../nodes/trails/PresBox"; -import { VideoBox } from "../../nodes/VideoBox"; -import { CreateImage } from "../../nodes/WebBoxRenderer"; -import { StyleProp } from "../../StyleProvider"; -import { CollectionDockingView } from "../CollectionDockingView"; -import { CollectionSubView } from "../CollectionSubView"; -import { TreeViewType } from "../CollectionTreeView"; -import { CollectionViewType } from "../CollectionView"; -import { TabDocView } from "../TabDocView"; -import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; -import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; -import "./CollectionFreeFormView.scss"; -import { MarqueeView } from "./MarqueeView"; -import React = require("react"); -import e = require("connect-flash"); - +import { Bezier } from 'bezier-js'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; +import { DateField } from '../../../../fields/DateField'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; +import { List } from '../../../../fields/List'; +import { ObjectField } from '../../../../fields/ObjectField'; +import { RichTextField } from '../../../../fields/RichTextField'; +import { listSpec } from '../../../../fields/Schema'; +import { ScriptField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { ImageField } from '../../../../fields/URLField'; +import { TraceMobx } from '../../../../fields/util'; +import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; +import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; +import { DocServer } from '../../../DocServer'; +import { Docs, DocUtils } from '../../../documents/Documents'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager, dropActionType } from '../../../util/DragManager'; +import { HistoryUtil } from '../../../util/History'; +import { InteractionUtils } from '../../../util/InteractionUtils'; +import { LinkManager } from '../../../util/LinkManager'; +import { RecordingApi } from '../../../util/RecordingApi'; +import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { SearchUtil } from '../../../util/SearchUtil'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { ColorScheme } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Transform } from '../../../util/Transform'; +import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss'; +import { Timeline } from '../../animationtimeline/Timeline'; +import { ContextMenu } from '../../ContextMenu'; +import { GestureOverlay } from '../../GestureOverlay'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; +import { LightboxView } from '../../LightboxView'; +import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; +import { PresBox } from '../../nodes/trails/PresBox'; +import { VideoBox } from '../../nodes/VideoBox'; +import { CreateImage } from '../../nodes/WebBoxRenderer'; +import { StyleProp } from '../../StyleProvider'; +import { CollectionDockingView } from '../CollectionDockingView'; +import { CollectionSubView } from '../CollectionSubView'; +import { TreeViewType } from '../CollectionTreeView'; +import { CollectionViewType } from '../CollectionView'; +import { TabDocView } from '../TabDocView'; +import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; +import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; +import './CollectionFreeFormView.scss'; +import { MarqueeView } from './MarqueeView'; +import React = require('react'); +import e = require('connect-flash'); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -69,13 +68,15 @@ export type collectionFreeformViewProps = { noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them - dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. + dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @observer export class CollectionFreeFormView extends CollectionSubView>() { - public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive + public get displayName() { + return 'CollectionFreeFormView(' + this.props.Document.title?.toString() + ')'; + } // this makes mobx trace() statements more descriptive private _lastNudge: any; private _lastX: number = 0; @@ -90,27 +91,33 @@ export class CollectionFreeFormView extends CollectionSubView(); private _layoutPoolData = observable.map(); - private _layoutSizeData = observable.map(); + private _layoutSizeData = observable.map(); private _cachedPool: Map = new Map(); private _lastTap = 0; private _batch: UndoManager.Batch | undefined = undefined; // private isWritingMode: boolean = true; - // private writingModeDocs: Doc[] = []; + // private writingModeDocs: Doc[] = []; - private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; } - private get scaleFieldKey() { return this.props.scaleField || "_viewScale"; } - private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; } + private get isAnnotationOverlay() { + return this.props.isAnnotationOverlay; + } + private get scaleFieldKey() { + return this.props.scaleField || '_viewScale'; + } + private get borderWidth() { + return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; + } @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables - @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 + @observable _viewTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 @observable _hLines: number[] | undefined; @observable _vLines: number[] | undefined; @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. @observable _pullCoords: number[] = [0, 0]; - @observable _pullDirection: string = ""; + @observable _pullDirection: string = ''; @observable _showAnimTimeline = false; - @observable _clusterSets: (Doc[])[] = []; + @observable _clusterSets: Doc[][] = []; @observable _deleteList: DocumentView[] = []; @observable _timelineRef = React.createRef(); @observable _marqueeRef = React.createRef(); @@ -118,43 +125,57 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } + @computed get views() { + return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + } @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, - scale: !this.childDocs.length ? 1 : - Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), - this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) + scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)), }; } - @computed get fitContentsToBox() { return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; } - @computed get contentBounds() { - const cb = Cast(this.rootDoc.contentBounds, listSpec("number")); - return cb ? {x:cb[0], y:cb[1], r:cb[2], b: cb[3]} : - this.props.contentBounds?.() ?? aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); + @computed get fitContentsToBox() { + return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox) && !this.isAnnotationOverlay; + } + @computed get contentBounds() { + const cb = Cast(this.rootDoc.contentBounds, listSpec('number')); + return cb + ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } + : this.props.contentBounds?.() ?? + aggregateBounds( + this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), + NumCast(this.layoutDoc._xPadding, 10), + NumCast(this.layoutDoc._yPadding, 10) + ); + } + @computed get nativeWidth() { + return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + } + @computed get nativeHeight() { + return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } - @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } - @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); } @computed get cachedCenteringShiftX(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; - return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections + return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; - return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections + return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { - return Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); + return Transform.Identity() + .scale(1 / this.zoomScaling()) + .translate(this.panX(), this.panY()); } @computed get cachedGetContainerTransform(): Transform { return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); } @computed get cachedGetTransform(): Transform { - return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); + return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } - changeKeyFrame = (back=false) => { - const currentFrame = Cast(this.Document._currentFrame, "number", null); + changeKeyFrame = (back = false) => { + const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { this.Document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); @@ -167,8 +188,8 @@ export class CollectionFreeFormView extends CollectionSubView this._keyframeEditing = set; + }; + @action setKeyFrameEditing = (set: boolean) => (this._keyframeEditing = set); getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); @@ -179,32 +200,35 @@ export class CollectionFreeFormView extends CollectionSubView !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; - reverseNativeScaling = () => this.fitContentsToBox ? true : false; + }; + freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined); + reverseNativeScaling = () => (this.fitContentsToBox ? true : false); // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. - // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image + // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1)); zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); - contentTransform = () => !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 ? "" : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; + contentTransform = () => + !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1 + ? '' + : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; getTransform = () => this.cachedGetTransform.copy(); getLocalTransform = () => this.cachedGetLocalTransform.copy(); getContainerTransform = () => this.cachedGetContainerTransform.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); isAnyChildContentActive = () => this.props.isAnyChildContentActive(); 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 + 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); - } + }; selectDocuments = (docs: Doc[]) => { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true)); - } + }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; if (newBox instanceof Doc) { - if (retVal = (this.props.addDocument?.(newBox) || false)) { + if ((retVal = this.props.addDocument?.(newBox) || false)) { this.bringToFront(newBox); this.updateCluster(newBox); } @@ -213,14 +237,14 @@ export class CollectionFreeFormView extends CollectionSubView newBox[field]); CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[`${field}-indexed`]); CollectionFreeFormDocumentView.animFields.forEach(field => delete newBox[field]); delete newBox.activeFrame; - CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== "opacity" && (newBox[field] = vals[i])); + CollectionFreeFormDocumentView.animFields.forEach((field, i) => field !== 'opacity' && (newBox[field] = vals[i])); } } if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) { @@ -228,13 +252,13 @@ export class CollectionFreeFormView extends CollectionSubView= -1e-4 && curTime <= endTime); + return dispTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime); } @action @@ -246,8 +270,11 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - zsorted.forEach((doc, index) => doc.zIndex = doc.isInkMask ? 5000 : index + 1); + const zsorted = this.childLayoutPairs + .map(pair => pair.layout) + .slice() + .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1)); const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000)); const dropPos = this.Document._currentFrame !== undefined ? [dvals.x || 0, dvals.y || 0] : [NumCast(refDoc.x), NumCast(refDoc.y)]; for (let i = 0; i < docDragData.droppedDocuments.length; i++) { @@ -267,8 +294,8 @@ export class CollectionFreeFormView extends CollectionSubView { return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); - } + }; pickCluster(probe: number[]) { - return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); - if (grouping !== -1) { - const layoutDoc = Doc.Layout(cd); - const cx = NumCast(cd.x) - this._clusterDistance; - const cy = NumCast(cd.y) - this._clusterDistance; - const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; - const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; - return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; - } - return cluster; - }, -1); + return this.childLayoutPairs + .map(pair => pair.layout) + .reduce((cluster, cd) => { + const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1); + if (grouping !== -1) { + const layoutDoc = Doc.Layout(cd); + const cx = NumCast(cd.x) - this._clusterDistance; + const cy = NumCast(cd.y) - this._clusterDistance; + const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance; + const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; + } + return cluster; + }, -1); } tryDragCluster(e: PointerEvent | TouchEvent, cluster: number) { @@ -338,10 +369,16 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster); const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 }; - const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? "alias" : undefined); + const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined); de.moveDocument = this.props.moveDocument; de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); - DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, ptsParent.clientX, ptsParent.clientY, { hideSource: !de.dropAction }); + DragManager.StartDocumentDrag( + clusterDocs.map(v => v.ContentDiv!), + de, + ptsParent.clientX, + ptsParent.clientY, + { hideSource: !de.dropAction } + ); return true; } } @@ -364,12 +401,16 @@ export class CollectionFreeFormView extends CollectionSubView this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); const preferredInd = NumCast(docFirst.cluster); - docs.map(doc => doc.cluster = -1); - docs.map(doc => this._clusterSets.map((set, i) => set.map(member => { - if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { - docFirst.cluster = i; - } - }))); + docs.map(doc => (doc.cluster = -1)); + docs.map(doc => + this._clusterSets.map((set, i) => + set.map(member => { + if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + docFirst.cluster = i; + } + }) + ) + ); if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { docFirst.cluster = preferredInd; } @@ -385,7 +426,7 @@ export class CollectionFreeFormView extends CollectionSubView this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc)); + docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc)); } childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child)); } @@ -399,11 +440,13 @@ export class CollectionFreeFormView extends CollectionSubView Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); const preferredInd = NumCast(doc.cluster); doc.cluster = -1; - this._clusterSets.forEach((set, i) => set.forEach(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { - doc.cluster = i; - } - })); + this._clusterSets.forEach((set, i) => + set.forEach(member => { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + doc.cluster = i; + } + }) + ); if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } @@ -423,7 +466,7 @@ export class CollectionFreeFormView extends CollectionSubView, props: Opt, property: string) => { - let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 + let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (property !== StyleProp.BackgroundColor) return styleProp; const cluster = NumCast(doc?.cluster); if (this.Document._useClusters) { @@ -431,15 +474,15 @@ export class CollectionFreeFormView extends CollectionSubView doc && this.updateCluster(doc)); } else { // choose a cluster color from a palette - const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"]; + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; styleProp = colors[cluster % colors.length]; const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.map(s => styleProp = StrCast(s.backgroundColor)); + set?.map(s => (styleProp = StrCast(s.backgroundColor))); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; - } + }; trySelectCluster = (addToSel: boolean) => { if (this._hitCluster !== -1) { @@ -449,30 +492,32 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointerup", this.onPenUp); - const currentCol = DocListCast(this.rootDoc.currentInkDoc) + document.removeEventListener('pointerup', this.onPenUp); + const currentCol = DocListCast(this.rootDoc.currentInkDoc); const rootDocList = DocListCast(this.rootDoc.data); currentCol.push(rootDocList[rootDocList.length - 1]); console.log(currentCol); this._batch?.end(); } - } + }; @action onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) { - if (!e.nativeEvent.cancelBubble && + if ( + !e.nativeEvent.cancelBubble && !this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && - !InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { + !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) + ) { switch (CurrentUserUtils.ActiveTool) { case InkTool.Highlighter: break; @@ -482,23 +527,23 @@ export class CollectionFreeFormView extends CollectionSubView) => { @@ -517,14 +562,13 @@ export class CollectionFreeFormView extends CollectionSubView(); @@ -534,8 +578,13 @@ export class CollectionFreeFormView extends CollectionSubView this.props.removeDocument?.(d)); e.stopPropagation(); break; @@ -573,17 +622,17 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.max(...ge.points.map(p => p.Y))); - const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color); - const sets = setDocs.map((sd) => { + const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); + const sets = setDocs.map(sd => { return Cast(sd.text, RichTextField)?.Text as string; }); if (sets.length && sets[0]) { this._wordPalette.clear(); const colors = setDocs.map(sd => FieldValue(sd.color) as string); - sets.forEach((st: string, i: number) => st.split(",").forEach(word => this._wordPalette.set(word, colors[i]))); + sets.forEach((st: string, i: number) => st.split(',').forEach(word => this._wordPalette.set(word, colors[i]))); } const inks = this.getActiveDocuments().filter(doc => { - if (doc.type === "ink") { + if (doc.type === 'ink') { const l = NumCast(doc.x); const r = l + doc[WidthSym](); const t = NumCast(doc.y); @@ -599,15 +648,15 @@ export class CollectionFreeFormView extends CollectionSubView pd.X) ?? [0]); - const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); + const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); if (d) { strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top }))); } }); - CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { - const wordResults = results.filter((r: any) => r.category === "inkWord"); + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => { + const wordResults = results.filter((r: any) => r.category === 'inkWord'); for (const word of wordResults) { const indices: number[] = word.strokeIds; indices.forEach(i => { @@ -619,8 +668,7 @@ export class CollectionFreeFormView extends CollectionSubView(uniqueColors); if (this._wordPalette.has(word.recognizedText.toLowerCase())) { inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase()); - } - else if (word.alternates) { + } else if (word.alternates) { for (const alt of word.alternates) { if (this._wordPalette.has(alt.recognizedString.toLowerCase())) { inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase()); @@ -641,27 +689,27 @@ export class CollectionFreeFormView extends CollectionSubView { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointermove", this.onEraserMove); - document.removeEventListener("pointerup", this.onEraserUp); + document.removeEventListener('pointermove', this.onEraserMove); + document.removeEventListener('pointerup', this.onEraserUp); this._deleteList.forEach(ink => ink.props.removeDocument?.(ink.rootDoc)); this._deleteList = []; this._batch?.end(); } - } + }; @action onPointerUp = (e: PointerEvent): void => { if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); } - } + }; onClick = (e: React.MouseEvent) => { if (this.onBrowseClickHandler()) { @@ -670,10 +718,10 @@ export class CollectionFreeFormView extends CollectionSubView { + pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => { const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true); this._lastX = e.clientX; this._lastY = e.clientY; - } + }; /** * Erases strokes by intersecting them with an invisible "eraser stroke". @@ -703,14 +751,16 @@ export class CollectionFreeFormView extends CollectionSubView { if (!this._deleteList.includes(intersect.inkView)) { this._deleteList.push(intersect.inkView); - SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || "1"); - SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || "black"); + SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1'); + SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black'); // create a new curve by appending all curves of the current segment together in order to render a single new stroke. - !e.shiftKey && this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => - GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Stroke, - segment.reduce((data, curve) => [...data, ...curve.points - .map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 }) - ], [] as PointData[]))); + !e.shiftKey && + this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment => + GestureOverlay.Instance.dispatchGesture( + GestureUtils.Gestures.Stroke, + segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]) + ) + ); // Lower ink opacity to give the user a visual indicator of deletion. intersect.inkView.layoutDoc.opacity = 0.5; } @@ -720,7 +770,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -730,45 +780,54 @@ export class CollectionFreeFormView extends CollectionSubView { + getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => { const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) }; const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! })) - .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap - eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom && - eraserMax.X >= inkViewBounds.left && eraserMax.Y >= inkViewBounds.top) + .filter( + ({ inkViewBounds }) => + inkViewBounds && // bounding box of eraser segment and ink stroke overlap + eraserMin.X <= inkViewBounds.right && + eraserMin.Y <= inkViewBounds.bottom && + eraserMax.X >= inkViewBounds.left && + eraserMax.Y >= inkViewBounds.top + ) .reduce((intersections, { inkStroke, inkView }) => { const { inkData } = inkStroke.inkScaledData(); // Convert from screen space to ink space for the intersection. const prevPointInkSpace = inkStroke.ptFromScreen(lastPoint); const currPointInkSpace = inkStroke.ptFromScreen(currPoint); for (var i = 0; i < inkData.length - 3; i += 4) { - const intersects = Array.from(new Set(InkField.Segment(inkData, i).intersects({ // compute all unique intersections - p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, - p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y } - }) as (number | string)[])); // convert to more manageable union array type + const intersects = Array.from( + new Set( + InkField.Segment(inkData, i).intersects({ + // compute all unique intersections + p1: { x: prevPointInkSpace.X, y: prevPointInkSpace.Y }, + p2: { x: currPointInkSpace.X, y: currPointInkSpace.Y }, + }) as (number | string)[] + ) + ); // convert to more manageable union array type // return tuples of the inkingStroke intersected, and the t value of the intersection - intersections.push(...intersects.map(t => ({ inkView, t: (+t) + Math.floor(i / 4) })));// convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve + intersections.push(...intersects.map(t => ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } return intersections; - }, [] as { t: number, inkView: DocumentView }[]); - } + }, [] as { t: number; inkView: DocumentView }[]); + }; /** * Performs segmentation of the ink stroke - creates "segments" or subsections of the current ink stroke at points in which the @@ -805,11 +864,11 @@ export class CollectionFreeFormView extends CollectionSubView (inkData.length / 4)) { + if (excludeT < startSegmentT || excludeT > inkData.length / 4) { segment.length && segments.push(segment); } return segments; - } + }; /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all @@ -837,13 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); curve.intersects(otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. - const t = +val.toString().split("/")[0]; - if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). + const t = +val.toString().split('/')[0]; + if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). }); } }); return tVals; - } + }; handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent) => { if (!e.cancelBubble) { @@ -853,8 +912,8 @@ export class CollectionFreeFormView extends CollectionSubView only want to do this when collection is selected' @@ -864,7 +923,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { // pinch zooming @@ -887,7 +946,7 @@ export class CollectionFreeFormView extends CollectionSubView) => { @@ -934,21 +994,20 @@ export class CollectionFreeFormView extends CollectionSubView { switch (this._pullDirection) { - case "left": case "right": case "top": case "bottom": - CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection); + case 'left': + case 'right': + case 'top': + case 'bottom': + CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: 'New Collection' }), this._pullDirection); } - this._pullDirection = ""; + this._pullDirection = ''; this._pullCoords = [0, 0]; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); this.removeMoveListeners(); this.removeEndListeners(); - } + }; @action zoom = (pointX: number, pointY: number, deltaY: number): void => { if (this.Document._isGroup) return; - let deltaScale = deltaY > 0 ? (1 / 1.05) : 1.05; + let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.getTransform().transformPoint(pointX, pointY); const invTransform = this.getLocalTransform().inverse(); @@ -996,27 +1058,28 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; - if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) + return; + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { + // things that can scroll vertically should do that instead of zooming e.stopPropagation(); - } - else if (this.props.isContentActive(true) && !this.Document._isGroup) { + } else if (this.props.isContentActive(true) && !this.Document._isGroup) { e.stopPropagation(); e.preventDefault(); !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? } - } + }; @action setPan(panX: number, panY: number, panTime: number = 0, clamp: boolean = false) { // set the current respective FFview to the tab being panned. - (Doc.UserDoc()?.presentationMode === 'recording') && RecordingApi.Instance.setRecordingFFView(this); + Doc.UserDoc()?.presentationMode === 'recording' && RecordingApi.Instance.setRecordingFFView(this); // TODO: make this based off the specific recording FFView - (Doc.UserDoc()?.presentationMode === 'none') && RecordingApi.Instance.setPlayFFView(this); + Doc.UserDoc()?.presentationMode === 'none' && RecordingApi.Instance.setPlayFFView(this); if (Doc.UserDoc()?.presentationMode === 'watching') { RecordingApi.Instance.pauseVideoAndMovements(); Doc.UserDoc().presentationMode = 'none'; @@ -1026,24 +1089,30 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(doc => doc instanceof Doc); - const measuredDocs = docs.map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ""), size: this.childSizeProviderUnmemoized(doc, "") })) - .filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! })); + const measuredDocs = docs + .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') })) + .filter(({ pos, size }) => pos && size) + .map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { - const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content - ({ - xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, - yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) } - }) - , { + const ranges = measuredDocs.reduce( + ( + { xrange, yrange }, + { pos, size } // computes range of content + ) => ({ + xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) }, + yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }, + }), + { xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, - yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE } - }); + yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }, + } + ); const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()]; - if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds - else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2; - if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2; - else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2; + if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds + else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2; + if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2; + else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2; } } if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.Document)) { @@ -1052,10 +1121,8 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || - this.props.ContainingCollectionDoc._panX !== undefined) { // bcz: this isn't ideal, but want to try it out... - this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(), - NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), nudgeTime, true); + if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) { + // bcz: this isn't ideal, but want to try it out... + this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true); this._lastNudge && clearTimeout(this._lastNudge); - this._lastNudge = setTimeout(action(() => this._viewTransition = 0), nudgeTime); + this._lastNudge = setTimeout( + action(() => (this._viewTransition = 0)), + nudgeTime + ); return true; } return false; - } + }; @action bringToFront = (doc: Doc, sendToBack?: boolean) => { @@ -1090,12 +1159,15 @@ export class CollectionFreeFormView extends CollectionSubView this._viewTransition = 0), this._viewTransition = transitionTime); // set transition to be smooth, then reset + setTimeout( + action(() => (this._viewTransition = 0)), + (this._viewTransition = transitionTime) + ); // set transition to be smooth, then reset const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]); @@ -1110,7 +1182,7 @@ export class CollectionFreeFormView extends CollectionSubView this._viewTransition = 0); + runInAction(() => (this._viewTransition = 0)); } return resetView; }; - const xf = !cantTransform ? Transform.Identity() : - this.props.isAnnotationOverlay ? - new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) - : - new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), - NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); + const xf = !cantTransform + ? Transform.Identity() + : this.props.isAnnotationOverlay + ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc)) + : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1); this.props.focus(cantTransform ? doc : this.rootDoc, { ...options, docTransform: xf, - afterFocus: (didFocus: boolean) => new Promise(res => - setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))) + afterFocus: (didFocus: boolean) => new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), }); } - } + }; calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { const layoutdoc = Doc.Layout(doc); @@ -1185,11 +1256,11 @@ export class CollectionFreeFormView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); @@ -1217,190 +1287,215 @@ export class CollectionFreeFormView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { + if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); - const below = !e.altKey && e.key !== "Tab"; + const below = !e.altKey && e.key !== 'Tab'; const layoutKey = StrCast(docView.LayoutFieldKey); const newDoc = Doc.MakeCopy(docView.rootDoc, true); const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10; else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10; - if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) { newDoc[layoutKey] = docView.rootDoc[layoutKey]; } Doc.GetProto(newDoc).text = undefined; FormattedTextBox.SelectOnLoad = newDoc[Id]; return this.addDocument?.(newDoc); } - } + }; pointerEvents = () => { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? "none" : - this.props.childPointerEvents ? "all" : - (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); + const pointerEvents = this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ? 'all' : this.props.viewDefDivClick || (engine === 'pass' && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.(); return pointerEvents; - } + }; getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; - return ; + return ( + + ); } addDocTab = action((doc: Doc, where: string) => { - if (where === "inParent") { - ((doc instanceof Doc) ? [doc] : doc).forEach(doc => { + if (where === 'inParent') { + (doc instanceof Doc ? [doc] : 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) || false; } - if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + if (where === 'inPlace' && this.layoutDoc.isInPlaceContainer) { this.dataDoc[this.props.fieldKey] = doc instanceof Doc ? doc : new List(doc as any as Doc[]); return true; } return this.props.addDocTab(doc, where); }); - getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc }): PoolData { + getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData { const layoutDoc = Doc.Layout(params.pair.layout); const { z, color, zIndex } = params.pair.layout; - const { x, y, opacity } = this.Document._currentFrame === undefined ? - { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } : - CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame)); + const { x, y, opacity } = + this.Document._currentFrame === undefined + ? { x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } + : CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame)); return { - x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), - transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null), - width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: "" + x: NumCast(x), + y: NumCast(y), + z: Cast(z, 'number'), + color: StrCast(color), + zIndex: Cast(zIndex, 'number'), + transition: StrCast(layoutDoc.dataTransition), + opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number', null), + width: Cast(layoutDoc._width, 'number'), + height: Cast(layoutDoc._height, 'number'), + pair: params.pair, + replica: '', }; } onViewDefDivClick = (e: React.MouseEvent, payload: any) => { (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload }); e.stopPropagation(); - } + }; viewDefsToJSX = (views: ViewDefBounds[]) => { return !Array.isArray(views) ? [] : views.filter(ele => this.viewDefToJSX(ele)).map(ele => this.viewDefToJSX(ele)!); - } + }; viewDefToJSX(viewDef: ViewDefBounds): Opt { const { x, y, z } = viewDef; const color = StrCast(viewDef.color); - const width = Cast(viewDef.width, "number"); - const height = Cast(viewDef.height, "number"); + const width = Cast(viewDef.width, 'number'); + const height = Cast(viewDef.height, 'number'); const transform = `translate(${x}px, ${y}px)`; - if (viewDef.type === "text") { - const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below - const fontSize = Cast(viewDef.fontSize, "string"); - return [text, x, y].some(val => val === undefined) ? undefined : - { - ele:
- {text} -
, - bounds: viewDef - }; - } else if (viewDef.type === "div") { - return [x, y].some(val => val === undefined) ? undefined : - { - ele:
this.onViewDefDivClick(e, viewDef)} - style={{ width, height, backgroundColor: color, transform }} />, - bounds: viewDef - }; + if (viewDef.type === 'text') { + const text = Cast(viewDef.text, 'string'); // don't use NumCast, StrCast, etc since we want to test for undefined below + const fontSize = Cast(viewDef.fontSize, 'string'); + return [text, x, y].some(val => val === undefined) + ? undefined + : { + ele: ( +
+ {text} +
+ ), + bounds: viewDef, + }; + } else if (viewDef.type === 'div') { + return [x, y].some(val => val === undefined) + ? undefined + : { + ele: ( +
this.onViewDefDivClick(e, viewDef)} + style={{ width, height, backgroundColor: color, transform }} + /> + ), + bounds: viewDef, + }; } } - - renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) { - return !this._renderCutoffData.get(doc[Id] + ""); - }.bind(this)); - + renderCutoffProvider = computedFn( + function renderCutoffProvider(this: any, doc: Doc) { + return !this._renderCutoffData.get(doc[Id] + ''); + }.bind(this) + ); childPositionProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutPoolData.get(doc[Id] + (replica || "")); - } - childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { - return this._layoutPoolData.get(doc[Id] + (replica || "")); - }.bind(this)); + return this._layoutPoolData.get(doc[Id] + (replica || '')); + }; + childDataProvider = computedFn( + function childDataProvider(this: any, doc: Doc, replica: string) { + return this._layoutPoolData.get(doc[Id] + (replica || '')); + }.bind(this) + ); childSizeProviderUnmemoized = (doc: Doc, replica: string) => { - return this._layoutSizeData.get(doc[Id] + (replica || "")); - } - childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) { - return this._layoutSizeData.get(doc[Id] + (replica || "")); - }.bind(this)); - - doEngineLayout(poolData: Map, - engine: ( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]), - engineProps: any) => ViewDefResult[] + return this._layoutSizeData.get(doc[Id] + (replica || '')); + }; + childSizeProvider = computedFn( + function childSizeProvider(this: any, doc: Doc, replica: string) { + return this._layoutSizeData.get(doc[Id] + (replica || '')); + }.bind(this) + ); + + doEngineLayout( + poolData: Map, + engine: (poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] ) { return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps); } doFreeformLayout(poolData: Map) { - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => - poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); + this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => poolData.set(pair.layout[Id], this.getCalculatedPositions({ pair, index: i, collection: this.Document }))); return [] as ViewDefResult[]; } - @computed get layoutEngine() { return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); } + @computed get layoutEngine() { + return this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); + } @computed get doInternalLayoutComputation() { TraceMobx(); const newPool = new Map(); switch (this.layoutEngine) { - case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; - case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; - case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; - case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; + case 'pass': + return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; + case 'timeline': + return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; + case 'pivot': + return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; + case 'starburst': + return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) }; } return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } @@ -1424,15 +1519,19 @@ export class CollectionFreeFormView extends CollectionSubView this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); - Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) => - elements.push({ - ele: this.getChildDocView(entry[1]), - bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) - })); + Array.from(newPool.entries()) + .filter(entry => this.isCurrent(entry[1].pair.layout)) + .forEach((entry, i) => + elements.push({ + ele: this.getChildDocView(entry[1]), + bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica), + }) + ); - if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar + if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) { + // don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling()); - else this.props.Document[this.scaleFieldKey] = Math.max(1,this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey])); + else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey])); } this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); @@ -1453,60 +1552,69 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: "ViewSpec - " + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); const proto = Doc.GetProto(anchor); - proto[ViewSpecPrefix + "_viewType"] = this.layoutDoc._viewType; + proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); - if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); } else { - this.dataDoc[this.props.fieldKey + "-annotations"] = new List([anchor]); + this.dataDoc[this.props.fieldKey + '-annotations'] = new List([anchor]); } return anchor; - } + }; @action componentDidMount() { super.componentDidMount?.(); this.props.setContentView?.(this); - setTimeout(action(() => { - this._firstRender = false; - this._disposers.layoutComputation = reaction(() => this.doLayoutComputation, - (elements) => this._layoutElements = elements || [], - { fireImmediately: true, name: "doLayout" }); - - this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); - - this._disposers.groupBounds = reaction(() => { - if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { - const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); - return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); - } - return undefined; - }, - (cbounds) => { - if (cbounds) { - const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; - const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; - const pbounds = { - x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], - r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1] - }; - this.layoutDoc._width = (pbounds.r - pbounds.x); - this.layoutDoc._height = (pbounds.b - pbounds.y); - this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; - this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; - this.layoutDoc.x = pbounds.x; - this.layoutDoc.y = pbounds.y; - } - }, { fireImmediately: true }); - })); + setTimeout( + action(() => { + this._firstRender = false; + this._disposers.layoutComputation = reaction( + () => this.doLayoutComputation, + elements => (this._layoutElements = elements || []), + { fireImmediately: true, name: 'doLayout' } + ); + + this._marqueeRef.current?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); + + this._disposers.groupBounds = reaction( + () => { + if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) { + const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() })); + return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); + } + return undefined; + }, + cbounds => { + if (cbounds) { + const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2]; + const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)]; + const pbounds = { + x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], + y: (cbounds.y - p[1]) * this.zoomScaling() + c[1], + r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], + b: (cbounds.b - p[1]) * this.zoomScaling() + c[1], + }; + this.layoutDoc._width = pbounds.r - pbounds.x; + this.layoutDoc._height = pbounds.b - pbounds.y; + this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2; + this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2; + this.layoutDoc.x = pbounds.x; + this.layoutDoc.y = pbounds.y; + } + }, + { fireImmediately: true } + ); + }) + ); } static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { @@ -1516,15 +1624,15 @@ export class CollectionFreeFormView extends CollectionSubView CollectionFreeFormView.UpdateIcon( - this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), - this.props.docViewPath().lastElement().ContentDiv!, - this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), - this.props.PanelWidth(), this.props.PanelHeight(), 0, 1, false, "", - (iconFile, nativeWidth, nativeHeight) => { - this.dataDoc.icon = new ImageField(iconFile); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }); + updateIcon = () => + CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + '-icon' + new Date().getTime(), + this.props.docViewPath().lastElement().ContentDiv!, + this.layoutDoc[WidthSym](), + this.layoutDoc[HeightSym](), + this.props.PanelWidth(), + this.props.PanelHeight(), + 0, + 1, + false, + '', + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc['icon-nativeWidth'] = nativeWidth; + this.dataDoc['icon-nativeHeight'] = nativeHeight; + } + ); public static UpdateIcon( - filename:string, docViewContent:HTMLElement, - width: number, height: number, - panelWidth:number, panelHeight: number, - scrollTop:number, + filename: string, + docViewContent: HTMLElement, + width: number, + height: number, + panelWidth: number, + panelHeight: number, + scrollTop: number, realNativeHeight: number, noSuffix: boolean, - replaceRootFilename: string| undefined, - cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) - { + replaceRootFilename: string | undefined, + cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any + ) { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; newDiv.style.width = width.toString(); newDiv.style.height = height.toString(); @@ -1563,15 +1682,8 @@ export class CollectionFreeFormView extends CollectionSubView { + return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) + .then(async (data_url: any) => { const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename); cb(returnedFilename as string, nativeWidth, nativeHeight); }) @@ -1582,13 +1694,13 @@ export class CollectionFreeFormView extends CollectionSubView disposer?.()); - this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); + this._marqueeRef.current?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any); } @action onCursorMove = (e: React.PointerEvent) => { // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); - } + }; @action onDragAutoScroll = (e: CustomEvent) => { @@ -1608,7 +1720,7 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1618,9 +1730,9 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1629,73 +1741,88 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(doc._height))) + 20; const dim = Math.ceil(Math.sqrt(docs.length)); docs.forEach((doc, i) => { - doc.x = NumCast(this.Document._panX) + (i % dim) * width - width * dim / 2; - doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - height * dim / 2; + doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2; + doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2; }); - } + }; @undoBatch - toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight) + toggleNativeDimensions = () => Doc.toggleNativeDimensions(this.layoutDoc, 1, this.nativeWidth, this.nativeHeight); onContextMenu = (e: React.MouseEvent) => { if (this.props.isAnnotationOverlay || this.props.Document.annotationOn || !ContextMenu.Instance) return; - const appearance = ContextMenu.Instance.findByDescription("Appearance..."); - const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; - appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); - !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); + const appearance = ContextMenu.Instance.findByDescription('Appearance...'); + const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; + appearanceItems.push({ + description: 'Reset View', + event: () => { + this.props.Document._panX = this.props.Document._panY = 0; + this.props.Document[this.scaleFieldKey] = 1; + }, + icon: 'compress-arrows-alt', + }); + !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); + appearanceItems.push({ + description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`, + event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox), + icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt', + }); + appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinDocView: true, panelWidth: this.props.PanelWidth(), panelHeight: this.props.PanelHeight() }), icon: 'map-pin' }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); - this.props.ContainingCollectionView && - appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); + this.props.ContainingCollectionView && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); - this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" }); + this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' }); // this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); - !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; - !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); - - const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); - const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.noviceMode ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? "Hide" : "Show") + " Snap Lines", event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: "compress-arrows-alt" }) : null; - !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; - !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); - - const options = ContextMenu.Instance.findByDescription("Options..."); - const optionItems = options && "subitems" in options ? options.subitems : []; - !this.props.isAnnotationOverlay && !Doc.noviceMode && - optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" }); - this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); + !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null; + !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); + + const viewctrls = ContextMenu.Instance.findByDescription('UI Controls...'); + const viewCtrlItems = viewctrls && 'subitems' in viewctrls ? viewctrls.subitems : []; + !Doc.noviceMode + ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' }) + : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null; + !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' }); + + const options = ContextMenu.Instance.findByDescription('Options...'); + const optionItems = options && 'subitems' in options ? options.subitems : []; + !this.props.isAnnotationOverlay && + !Doc.noviceMode && + optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' }); + this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' }); if (!Doc.noviceMode) { - optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); + optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' }); } - !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); - const mores = ContextMenu.Instance.findByDescription("More..."); - const moreItems = mores && "subitems" in mores ? mores.subitems : []; + !options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); + const mores = ContextMenu.Instance.findByDescription('More...'); + const moreItems = mores && 'subitems' in mores ? mores.subitems : []; if (!Doc.noviceMode) { e.persist(); - moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) }); - moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); + moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); } - !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); - } + !mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' }); + }; importDocument = (x: number, y: number) => { - const input = document.createElement("input"); - input.type = "file"; - input.accept = ".zip"; + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.zip'; input.onchange = _e => { - input.files && Doc.importDocument(input.files[0]).then(doc => { - if (doc instanceof Doc) { - const [xx, yy] = this.getTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(doc);} - }); + input.files && + Doc.importDocument(input.files[0]).then(doc => { + if (doc instanceof Doc) { + const [xx, yy] = this.getTransform().transformPoint(x, y); + (doc.x = xx), (doc.y = yy); + this.props.addDocument?.(doc); + } + }); }; input.click(); - } + }; @undoBatch @action @@ -1704,13 +1831,13 @@ export class CollectionFreeFormView extends CollectionSubView { @@ -1718,37 +1845,39 @@ export class CollectionFreeFormView extends CollectionSubView ({ 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 }) => intersectRect(docDims(doc), rect); + const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.getTransform().inverse(); - snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { - const { left, top, width, height } = docDims(doc); - const topLeftInScreen = invXf.transformPoint(left, top); - const docSize = invXf.transformDirection(width, height); + snappableDocs + .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)) + .forEach(doc => { + const { left, top, width, height } = docDims(doc); + const topLeftInScreen = invXf.transformPoint(left, top); + const docSize = invXf.transformDirection(width, height); - horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line - vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]);// right line - }); + 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) => { e.stopPropagation(); - } + }; incrementalRender = action(() => { if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) { const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); const loadIncrement = 5; for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) { - this._renderCutoffData.set(unrendered[i][Id] + "", true); + this._renderCutoffData.set(unrendered[i][Id] + '', true); } } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); @@ -1756,64 +1885,67 @@ export class CollectionFreeFormView extends CollectionSubView { this.incrementalRender(); - const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; - return [ - ...children, - ...this.views, - - ]; - } + const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : []; + return [...children, ...this.views, ]; + }; @computed get placeholder() { - return
- {this.props.Document.title?.toString()} -
; + return ( +
+ {this.props.Document.title?.toString()} +
+ ); } @computed get marqueeView() { TraceMobx(); - return 0 ? undefined : this.nudge} - addDocTab={this.addDocTab} - trySelectCluster={this.trySelectCluster} - activeDocuments={this.getActiveDocuments} - selectDocuments={this.selectDocuments} - addDocument={this.addDocument} - addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.getContainerTransform} - getTransform={this.getTransform} - isAnnotationOverlay={this.isAnnotationOverlay}> -
- {this.layoutDoc._backgroundGridShow ? -
0 ? undefined : this.nudge} + addDocTab={this.addDocTab} + trySelectCluster={this.trySelectCluster} + activeDocuments={this.getActiveDocuments} + selectDocuments={this.selectDocuments} + addDocument={this.addDocument} + addLiveTextDocument={this.addLiveTextBox} + getContainerTransform={this.getContainerTransform} + getTransform={this.getTransform} + isAnnotationOverlay={this.isAnnotationOverlay}> +
+ {this.layoutDoc._backgroundGridShow ? ( +
+ +
+ ) : null} +
: (null)} - - {this.children} - -
- {this._showAnimTimeline ? : (null)} - ; + isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} + transform={this.contentTransform} + zoomScaling={this.zoomScaling} + presPaths={BoolCast(this.Document.presPathView)} + progressivize={BoolCast(this.Document.editProgressivize)} + presPinView={BoolCast(this.Document.presPinView)} + transition={this._viewTransition ? `transform ${this._viewTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)} + viewDefDivClick={this.props.viewDefDivClick}> + {this.children} + +
+ {this._showAnimTimeline ? : null} +
+ ); } @computed get contentScaling() { @@ -1826,66 +1958,83 @@ export class CollectionFreeFormView extends CollectionSubView { //used for stacking and masonry view + protected createGroupEventsTarget = (ele: HTMLDivElement) => { + //used for stacking and masonry view this.groupDropDisposer?.(); if (ele) { this.groupDropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc, this.onInternalPreDrop.bind(this)); } - } + }; render() { TraceMobx(); const clientRect = this._mainCont?.getBoundingClientRect(); - return
e.preventDefault()} - onContextMenu={this.onContextMenu} - style={{ - pointerEvents: this.props.Document.type === DocumentType.MARKER ? "none" : // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach. - (SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())) ? "all" : this.props.pointerEvents?.() as any, - transform: `scale(${this.contentScaling || 1})`, - width: `${100 / (this.contentScaling || 1)}%`, - height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() - }}> - {this._firstRender ? - this.placeholder : this.marqueeView} - {this.props.noOverlay ? (null) : } - - -
e.preventDefault()} + onContextMenu={this.onContextMenu} style={{ - display: this._pullDirection ? "block" : "none", - top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto", - left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto", - width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0, - height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0, - + pointerEvents: + this.props.Document.type === DocumentType.MARKER + ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach. + : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) + ? 'all' + : (this.props.pointerEvents?.() as any), + transform: `scale(${this.contentScaling || 1})`, + width: `${100 / (this.contentScaling || 1)}%`, + height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> + {this._firstRender ? this.placeholder : this.marqueeView} + {this.props.noOverlay ? null : } + +
+ { + // uncomment to show snap lines +
+ + {this._hLines?.map(l => ( + + ))} + {this._vLines?.map(l => ( + + ))} + +
+ } + + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? ( +
+ ) : null}
- {// uncomment to show snap lines -
- - {this._hLines?.map(l => )} - {this._vLines?.map(l => )} - -
} - - {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? -
: (null)} -
; + ); } } @@ -1894,9 +2043,12 @@ interface CollectionFreeFormOverlayViewProps { } @observer -class CollectionFreeFormOverlayView extends React.Component{ +class CollectionFreeFormOverlayView extends React.Component { render() { - return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); + return this.props + .elements() + .filter(ele => ele.bounds?.z) + .map(ele => ele.ele); } } @@ -1914,50 +2066,50 @@ interface CollectionFreeFormViewPannableContentsProps { } @observer -class CollectionFreeFormViewPannableContents extends React.Component{ +class CollectionFreeFormViewPannableContents extends React.Component { @observable _drag: string = ''; //Adds event listener so knows pointer is down and moving onPointerDown = (e: React.PointerEvent): void => { e.stopPropagation(); e.preventDefault(); - this._drag = (e.target as any)?.id ?? ""; + this._drag = (e.target as any)?.id ?? ''; document.getElementById(this._drag) && setupMoveUpEvents(e.target, e, this.onPointerMove, emptyFunction, emptyFunction); - } + }; //Adjusts the value in NodeStore @action onPointerMove = (e: PointerEvent) => { const doc = document.getElementById('resizable'); - const toNumber = (original: number, delta: number) => original + (delta * this.props.zoomScaling()); + const toNumber = (original: number, delta: number) => original + delta * this.props.zoomScaling(); if (doc) { switch (this._drag) { - case "resizer-br": + case 'resizer-br': doc.style.width = toNumber(doc.offsetWidth, e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px'; break; - case "resizer-bl": + case 'resizer-bl': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; break; - case "resizer-tr": + case 'resizer-tr': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px'; doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; - case "resizer-tl": + case 'resizer-tl': doc.style.width = toNumber(doc.offsetWidth, -e.movementX) + 'px'; doc.style.height = toNumber(doc.offsetHeight, -e.movementY) + 'px'; doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; - case "resizable": + case 'resizable': doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; } return false; } return true; - } + }; // scale: NumCast(targetDoc._viewScale), @computed get zoomProgressivizeContainer() { @@ -1968,66 +2120,73 @@ class CollectionFreeFormViewPannableContents extends React.Component -
-
-
-
-
+
+
+
+
+
-
; +
+ ); } } @computed get zoomProgressivize() { - return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : (null); + return PresBox.Instance?.activeItem?.presPinView && PresBox.Instance.layoutDoc.presStatus === 'edit' ? this.zoomProgressivizeContainer : null; } @computed get progressivize() { - return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null); + return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : null; } @computed get presPaths() { - const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden"); - return !PresBox.Instance || !this.props.presPaths ? (null) : <> -
{PresBox.Instance.order}
- - - - - - - - - - - - - {PresBox.Instance.paths} - - ; + const presPaths = 'presPaths' + (this.props.presPaths ? '' : '-hidden'); + return !PresBox.Instance || !this.props.presPaths ? null : ( + <> +
{PresBox.Instance.order}
+ + + + + + + + + + + + + {PresBox.Instance.paths} + + + ); } render() { - return
{ - const target = e.target as any; - if (getComputedStyle(target)?.overflow === "visible") { // if collection is visible, then scrolling will mess things up since there are no scroll bars - target.scrollTop = target.scrollLeft = 0; - } - }} - style={{ - transform: this.props.transform(), - transition: this.props.transition, - width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection - //willChange: "transform" - }}> - {this.props.children()} - {this.presPaths} - {this.progressivize} - {this.zoomProgressivize} -
; + return ( +
{ + const target = e.target as any; + if (getComputedStyle(target)?.overflow === 'visible') { + // if collection is visible, then scrolling will mess things up since there are no scroll bars + target.scrollTop = target.scrollLeft = 0; + } + }} + style={{ + transform: this.props.transform(), + transition: this.props.transition, + width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection + //willChange: "transform" + }}> + {this.props.children()} + {this.presPaths} + {this.progressivize} + {this.zoomProgressivize} +
+ ); } } @@ -2044,63 +2203,73 @@ interface CollectionFreeFormViewBackgroundGridProps { } @observer class CollectionFreeFormBackgroundGrid extends React.Component { - - chooseGridSpace = (gridSpace: number): number => { if (!this.props.zoomScaling()) return 50; const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3; return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10); - } + }; render() { - const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50)); - const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling(); - const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling(); + const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50)); + const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling(); + const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling(); const renderGridSpace = gridSpace * this.props.zoomScaling(); const w = this.props.PanelWidth() + 2 * renderGridSpace; const h = this.props.PanelHeight() + 2 * renderGridSpace; - const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)"; - return { - const ctx = el?.getContext('2d'); - if (ctx) { - const Cx = this.props.cachedCenteringShiftX % renderGridSpace; - const Cy = this.props.cachedCenteringShiftY % renderGridSpace; - ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); - ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); - ctx.clearRect(0, 0, w, h); + const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)'; + return ( + { + const ctx = el?.getContext('2d'); if (ctx) { - ctx.strokeStyle = strokeStyle; - ctx.beginPath(); - for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { - ctx.moveTo(x, Cy - h); - ctx.lineTo(x, Cy + h); + const Cx = this.props.cachedCenteringShiftX % renderGridSpace; + const Cy = this.props.cachedCenteringShiftY % renderGridSpace; + ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling())); + ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]); + ctx.clearRect(0, 0, w, h); + if (ctx) { + ctx.strokeStyle = strokeStyle; + ctx.beginPath(); + for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) { + ctx.moveTo(x, Cy - h); + ctx.lineTo(x, Cy + h); + } + for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { + ctx.moveTo(Cx - w, y); + ctx.lineTo(Cx + w, y); + } + ctx.stroke(); } - for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { - ctx.moveTo(Cx - w, y); - ctx.lineTo(Cx + w, y); - } - ctx.stroke(); } - } - }} />; + }} + /> + ); } } export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { SelectionManager.DeselectAll(); dv.props.focus(dv.props.Document, { - willZoom: true, afterFocus: async (didMove) => { + willZoom: true, + afterFocus: async didMove => { if (!didMove) { const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; - const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || "_viewScale"] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview + const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5); } return ViewAdjustment.doNothing; - } + }, }); Doc.linkFollowHighlight(dv?.props.Document, false); } ScriptingGlobals.add(CollectionBrowseClick); -ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); -ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); \ No newline at end of file +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { + !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); +}); +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { + !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); +}); -- cgit v1.2.3-70-g09d2