diff options
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 128 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 10 |
2 files changed, 43 insertions, 95 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 57c943479..cafa8b38f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -36,7 +36,7 @@ import { GestureOverlay } from '../../GestureOverlay'; import { CtrlKey } from '../../GlobalKeyHandler'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke'; import { LightboxView } from '../../LightboxView'; -import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { CollectionFreeFormDocumentViewWrapper, CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -45,7 +45,6 @@ import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; -import { TabDocView } from '../TabDocView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; @@ -86,9 +85,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _hitCluster: number = -1; private _disposers: { [name: string]: IReactionDisposer } = {}; private _renderCutoffData = observable.map<string, boolean>(); - private _layoutPoolData = observable.map<string, PoolData>(); - private _layoutSizeData = observable.map<string, { width?: number; height?: number }>(); - private _cachedPool: Map<string, PoolData> = new Map(); private _batch: UndoManager.Batch | undefined = undefined; private _brushtimer: any; private _brushtimer1: any; @@ -109,7 +105,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return (this.props.viewField ?? '') + '_freeform_autoReset'; } - @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables + @computed get _layoutElements(): ViewDefResult[] { + const computation = this.doInternalLayoutComputation; + return this.doLayoutComputation(computation.newPool, computation.computedElementData); + } @observable _panZoomTransition: 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 _firstRender = false; // 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. could be used for performance improvement @observable _showAnimTimeline = false; @@ -120,7 +119,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - @computed get views() { + @computed get contentViews() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele); if (viewsMask.length) renderableEles.push(<div className={`collectionfreeformview-mask${this._layoutElements.some(ele => (ele.inkMask ?? 0) > 0) ? '' : '-empty'}`}>{viewsMask}</div>); @@ -637,7 +636,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._downTime = Date.now(); - if (e.button === 0 && !e.altKey && (!(e.ctrlKey && !e.metaKey) || Doc.UserDoc().freeformScrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) { + const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; + if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this.props.isContentActive(true)) { if ( !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) && @@ -1052,7 +1052,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc); const measuredDocs = docs - .map(doc => ({ pos: this.childPositionProviderUnmemoized(doc, ''), size: this.childSizeProviderUnmemoized(doc, '') })) + .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc.width), height: NumCast(doc.height) } })) .filter(({ pos, size }) => pos && size) .map(({ pos, size }) => ({ pos: pos!, size: size! })); if (measuredDocs.length) { @@ -1247,7 +1247,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.isContentActive() === false ? 'none' : this.props.pointerEvents?.()); - console.log(`${this.rootDoc.title} pe = ` + pointerevents); return pointerevents; } @@ -1258,16 +1257,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const childLayout = entry.pair.layout; const childData = entry.pair.data; return ( - <CollectionFreeFormDocumentView + <CollectionFreeFormDocumentViewWrapper + {...OmitKeys(entry, ['replica', 'pair']).omit} key={childLayout[Id] + (entry.replica || '')} DataDoc={childData} Document={childLayout} + dragStarting={this.dragStarting} + dragEnding={this.dragEnding} isGroupActive={this.props.isGroupActive} renderDepth={this.props.renderDepth + 1} - replica={entry.replica} hideDecorations={BoolCast(childLayout._layout_isSvg && childLayout.type === DocumentType.LINK)} suppressSetHeight={this.layoutEngine ? true : false} - renderCutoffProvider={this.renderCutoffProvider} + RenderCutoffProvider={this.renderCutoffProvider} CollectionFreeFormView={this} LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate} LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString} @@ -1295,8 +1296,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection docViewPath={this.props.docViewPath} styleProvider={this.clusterStyleProvider} dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType} - dataProvider={this.childDataProvider} - sizeProvider={this.childSizeProvider} bringToFront={this.bringToFront} layout_showTitle={this.props.childlayout_showTitle} dontRegisterView={this.props.dontRegisterView} @@ -1425,20 +1424,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }.bind(this) ); - childPositionProviderUnmemoized = (doc: Doc, replica: string) => this._layoutPoolData.get(doc[Id] + (replica || '')); - childDataProvider = computedFn( - function childDataProvider(this: any, doc: Doc, replica: string) { - return this.childPositionProviderUnmemoized(doc, replica); - }.bind(this) - ); - - childSizeProviderUnmemoized = (doc: Doc, replica: string) => this._layoutSizeData.get(doc[Id] + (replica || '')); - childSizeProvider = computedFn( - function childSizeProvider(this: any, doc: Doc, replica: string) { - return this.childSizeProviderUnmemoized(doc, replica); - }.bind(this) - ); - doEngineLayout( poolData: Map<string, PoolData>, engine: (poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) => ViewDefResult[] @@ -1467,46 +1452,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return { newPool, computedElementData: this.doFreeformLayout(newPool) }; } - @observable _numLoaded = 1; @action doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => { - const array = Array.from(newPool.entries()); - for (const entry of array) { - const lastPos = this._cachedPool.get(entry[0]); // last computed pos - const newPos = entry[1]; - if ( - !lastPos || - newPos.color !== lastPos.color || - newPos.backgroundColor !== lastPos.backgroundColor || - newPos.opacity !== lastPos.opacity || - newPos.x !== lastPos.x || - newPos.y !== lastPos.y || - newPos.z !== lastPos.z || - newPos.rotation !== lastPos.rotation || - newPos.zIndex !== lastPos.zIndex || - newPos.transition !== lastPos.transition || - newPos.pointerEvents !== lastPos.pointerEvents - ) { - this._layoutPoolData.set(entry[0], newPos); - } - if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) { - this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height }); - } - } - this._cachedPool.clear(); - Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements = computedElementData.slice(); Array.from(newPool.entries()) .filter(entry => this.isCurrent(entry[1].pair.layout)) - .forEach((entry, i) => { - const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica); - const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica); + .forEach(entry => elements.push({ ele: this.getChildDocView(entry[1]), - bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height }, + bounds: (entry[1].opacity === 0 ? { ...entry[1], width: 0, height: 0 } : { ...entry[1] }) as any, inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, - }); - }); + }) + ); this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); return elements; @@ -1580,12 +1537,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection { fireImmediately: true } ); - this._disposers.layoutComputation = reaction( - () => this.doInternalLayoutComputation, - ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)), - { fireImmediately: true, name: 'layoutComputationReaction' } - ); - this._disposers.active = reaction( () => this.isContentActive(), // if autoreset is on, then whenever the view is selected, it will be restored to it default pan/zoom positions active => !SnappingManager.GetIsDragging() && this.rootDoc[this.autoResetFieldKey] && active && this.resetView() @@ -1752,7 +1703,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return; } !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' }); - appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); + appearanceItems.push({ description: `Pin View`, event: () => this.props.pinToPres(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' }); !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' }); this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); @@ -1799,13 +1750,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection SnappingManager.clearSnapLines(); }; @action - dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, visited = new Set<Doc>()) => { + dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => { if (visited.has(this.rootDoc)) return; visited.add(this.rootDoc); showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document._isGroup)); - if (this.rootDoc._isGroup && this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { - this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited); - } const activeDocs = this.getActiveDocuments(); const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; @@ -1813,13 +1761,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to - activeDocs.forEach( - doc => - doc._isGroup && - SnappingManager.GetIsResizing() !== doc && - !DragManager.docsBeingDragged.includes(doc) && - (DocumentManager.Instance.getDocumentView(doc)?.ComponentView as CollectionFreeFormView)?.dragStarting(snapToDraggedDoc, false, visited) - ); + activeDocs + .filter(doc => doc._isGroup && SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)) + .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; @@ -1850,10 +1794,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); - get children() { - this.incrementalRender(); - const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : []; - return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />]; + // if a freeform view has any children, then the children will likely consist of a single child + // which will be a DocumentView. In this sitation, this freeform views acts as an annotation overlay for + // the underlying DocumentView and will pan and scoll with the underlying Documen tView. + @computed get underlayViews() { + return this.props.children ? [this.props.children] : []; } @computed get placeholder() { @@ -1889,6 +1834,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } @computed get pannableContents() { + this.incrementalRender(); return ( <CollectionFreeFormPannableContents rootDoc={this.rootDoc} @@ -1897,7 +1843,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection transform={this.PanZoomCenterXf} transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))} viewDefDivClick={this.props.viewDefDivClick}> - {this.children} + {this.underlayViews} + {this.contentViews} + <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormPannableContents> ); } @@ -2048,11 +1996,11 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY if (!focused) { const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined; let containers = dv.props.docViewPath(); - let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + let parFfview = dv.CollectionFreeFormView; for (var cont of containers) { - parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + parFfview = parFfview ?? cont.CollectionFreeFormView; } - while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.DocumentView?.().CollectionFreeFormView; const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 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.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); Doc.linkFollowHighlight(dv?.props.Document, false); @@ -2084,8 +2032,8 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) { ); }); ScriptingGlobals.add(function bringToFront() { - SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc)); + SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc)); }); ScriptingGlobals.add(function sendToBack(doc: Doc) { - SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true)); + SelectionManager.Views().forEach(view => view.CollectionFreeFormView?.bringToFront(view.rootDoc, true)); }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f831478a7..23f9bc69e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -22,7 +22,6 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { PreviewCursor } from '../../PreviewCursor'; import { SubCollectionViewProps } from '../CollectionSubView'; -import { TabDocView } from '../TabDocView'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; import React = require('react'); @@ -212,14 +211,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque // if (this.props.pointerEvents?.() === 'none') return; this._downX = this._lastX = e.clientX; this._downY = this._lastY = e.clientY; + const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; + if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; // allow marquee if ( e.button === 2 || //if right drag - e.altKey || // or alt drag - e.metaKey || // or alt drag - Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan // or pan mode + e.metaKey || // or meta drag + scrollMode === freeformScrollMode.Pan // or pan mode ) { // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) { this.setPreviewCursor(e.clientX, e.clientY, true, false, this.props.Document); @@ -403,7 +403,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action pinWithView = () => { - TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds }); + this.props.pinToPres(this.props.Document, { pinViewport: this.Bounds }); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); }; |
