diff options
| author | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-02 02:12:19 -0400 |
|---|---|---|
| committer | Sophie Zhang <sophie_zhang@brown.edu> | 2023-11-02 02:12:19 -0400 |
| commit | a1d00a36ef1afa97198a825bd25ebb4c5e598848 (patch) | |
| tree | e0c0454c99938562132794333a22e490e3e37cb9 /src/client/views/collections/collectionFreeForm | |
| parent | 78d8261522c0079b0298613a856547a9ac96ef50 (diff) | |
| parent | 84c15417f2247fc650a9f7b2c959479519bd3ebb (diff) | |
Merge branch 'master' into sophie-ai-images
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
4 files changed, 148 insertions, 220 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index c1c01eacb..d93e44ab7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -42,6 +42,7 @@ export interface PoolData { transition?: string; highlight?: boolean; replica: string; + pointerEvents?: string; // without this, toggling lockPosition of a group/collection in a freeform view won't update until something else invalidates the freeform view's documents forcing -- this is a problem with doLayoutComputation which makes a performance test to insure somethingChanged pair: { layout: Doc; data?: Doc }; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index c90fdf013..250760bd5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -54,10 +54,14 @@ } } + .presPathLabels { + pointer-events: none; + } svg.presPaths { position: absolute; z-index: 100000; overflow: visible; + pointer-events: none; } svg.presPaths-hidden { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bfc61f601..0c3033579 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -16,17 +16,18 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, emptyFunction, intersectRect, lightOrDark, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { InteractionUtils } from '../../../util/InteractionUtils'; +import { FollowLinkScript } from '../../../util/LinkFollower'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; -import { ColorScheme, freeformScrollMode } from '../../../util/SettingsManager'; +import { freeformScrollMode } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; @@ -51,7 +52,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); -import { FollowLinkScript } from '../../../util/LinkFollower'; export type collectionFreeformViewProps = { NativeWidth?: () => number; @@ -82,6 +82,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _lastY: number = 0; private _downX: number = 0; private _downY: number = 0; + private _downTime = 0; private _inkToTextStartX: number | undefined; private _inkToTextStartY: number | undefined; private _wordPalette: Map<string, string> = new Map<string, string>(); @@ -92,7 +93,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _layoutPoolData = observable.map<string, PoolData>(); private _layoutSizeData = observable.map<string, { width?: number; height?: number }>(); private _cachedPool: Map<string, PoolData> = new Map(); - private _lastTap = 0; private _batch: UndoManager.Batch | undefined = undefined; private _brushtimer: any; private _brushtimer1: any; @@ -118,8 +118,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @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 _hLines: number[] | undefined; - @observable _vLines: number[] | undefined; @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; @observable _clusterSets: Doc[][] = []; @@ -128,11 +126,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _marqueeRef: HTMLDivElement | null = null; @observable _marqueeViewRef = React.createRef<MarqueeView>(); @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. - @observable _brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; // highlighted region of freeform canvas used by presentations to indicate a region - - constructor(props: any) { - super(props); - } + @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region @computed get views() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); @@ -156,12 +150,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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?.width && !e.bounds.z).map(e => e.bounds!), - NumCast(this.layoutDoc._xPadding, 10), - NumCast(this.layoutDoc._yPadding, 10) - ); + : aggregateBounds( + this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), + NumCast(this.layoutDoc._xPadding, 10), + NumCast(this.layoutDoc._yPadding, 10) + ); } @computed get nativeWidth() { return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null))); @@ -317,6 +310,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection focus = (anchor: Doc, options: DocFocusOptions) => { if (this._lightboxDoc) return; + if (anchor === this.rootDoc) { + if (options.willZoomCentered && options.zoomScale) { + this.fitContentOnce(); + options.didMove = true; + } + } if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return; const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; @@ -462,9 +461,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return false; }; - onExternalDrop = (e: React.DragEvent) => { - return (pt => super.onExternalDrop(e, { x: pt[0], y: pt[1] }))(this.getTransform().transformPoint(e.pageX, e.pageY)); - }; + onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY)); pickCluster(probe: number[]) { return this.childLayoutPairs @@ -641,6 +638,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; + this._downTime = Date.now(); if (e.button === 0 && !e.altKey && !e.ctrlKey && 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 @@ -715,7 +713,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection points, ActiveIsInkMask(), { - title: 'ink stroke', + title: ge.gesture.toString(), x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2, y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2, _width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale, @@ -733,9 +731,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => DocCast(s.proto)?.type === DocumentType.RTF && s.color); - const sets = setDocs.map(sd => { - return Cast(sd.text, RichTextField)?.Text as string; - }); + const sets = setDocs.map(sd => 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); @@ -810,31 +806,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onClick = (e: React.MouseEvent) => { if (this._lightboxDoc) this._lightboxDoc = undefined; - if (this.onBrowseClickHandler()) { - if (this.props.DocumentView?.()) { - this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY }); - } - e.stopPropagation(); - e.preventDefault(); - } else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) { - if (e.shiftKey && (this.props.renderDepth === 0 || this.isContentActive())) { - if (Date.now() - this._lastTap < 300) { - // reset zoom of freeform view to 1-to-1 on a shift + double click - this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); - } + if (Utils.isClick(e.pageX, e.pageY, this._downX, this._downY, this._downTime)) { + if (this.onBrowseClickHandler()) { + this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView?.(), clientX: e.clientX, clientY: e.clientY }); + e.stopPropagation(); + e.preventDefault(); + } else if (this.isContentActive() && e.shiftKey) { + // reset zoom of freeform view to 1-to-1 on a shift + double click + this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); e.stopPropagation(); e.preventDefault(); } - this._lastTap = Date.now(); } }; @action scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => { PresBox.Instance?.pauseAutoPres(); - const dx = e.deltaX; - const dy = e.deltaY; - this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true); + this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0, true); }; @action @@ -1183,23 +1172,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack) { - const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); - docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0; - doc.zIndex = zfirst - 1; - } else if (doc.stroke_isInkMask) { + if (doc.stroke_isInkMask) { doc.zIndex = 5000; } else { - const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); - docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1; - if (docs.lastElement() !== doc) { - if (zlast - docs.length > 100) { - for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; - zlast = docs.length + 1; + // prettier-ignore + const docs = this.childLayoutPairs.map(pair => pair.layout) + .sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + if (sendToBack) { + const zfirst = docs.length ? NumCast(docs[0].zIndex) : 0; + doc.zIndex = zfirst - 1; + } else { + let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1; + if (docs.lastElement() !== doc) { + if (zlast - docs.length > 100) { + for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; + zlast = docs.length + 1; + } + doc.zIndex = zlast + 1; } - doc.zIndex = zlast + 1; } } }; @@ -1291,14 +1281,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.addDocument?.(newDoc); } }; - @computed get _pointerEvents() { + @computed get childPointerEvents() { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); const pointerEvents = DocumentView.Interacting ? 'none' : this.props.childPointerEvents?.() ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) || this.isContentActive() === false ? 'none' : this.props.pointerEvents?.()); return pointerEvents; } - pointerEvents = () => this._pointerEvents; + childPointerEventsFunc = () => this.childPointerEvents; childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; @@ -1308,8 +1298,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection key={childLayout[Id] + (entry.replica || '')} DataDoc={childData} Document={childLayout} + 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} CollectionFreeFormView={this} @@ -1327,7 +1319,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - isDocumentActive={this.props.childDocumentsActive?.() || this.rootDoc._isGroup ? this.props.isDocumentActive : this.isContentActive} + isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive} isContentActive={this.childContentsActive} focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus} addDocTab={this.addDocTab} @@ -1344,7 +1336,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection bringToFront={this.bringToFront} layout_showTitle={this.props.childlayout_showTitle} dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView} - pointerEvents={this.pointerEvents} + pointerEvents={this.childPointerEventsFunc} //fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this /> ); @@ -1412,6 +1404,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection height: _height, transition: StrCast(childDocLayout.dataTransition), pair: params.pair, + pointerEvents: Cast(childDoc.pointerEvents, 'string', null), replica: '', }; } @@ -1531,7 +1524,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection newPos.z !== lastPos.z || newPos.rotation !== lastPos.rotation || newPos.zIndex !== lastPos.zIndex || - newPos.transition !== lastPos.transition + newPos.transition !== lastPos.transition || + newPos.pointerEvents !== lastPos.pointerEvents ) { this._layoutPoolData.set(entry[0], newPos); somethingChanged = true; @@ -1625,17 +1619,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); this._disposers.active = reaction( - () => this.isContentActive(), - active => this.rootDoc[this.autoResetFieldKey] && !active && this.resetView() - ); - - this._disposers.fitContent = reaction( - () => this.rootDoc.fitContentOnce, - fitContentOnce => { - if (fitContentOnce) this.fitContentOnce(); - this.rootDoc.fitContentOnce = undefined; - }, - { fireImmediately: true, name: 'fitContent' } + () => 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() ); }) ); @@ -1826,16 +1811,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' }); @@ -1871,23 +1851,38 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action - setupDragLines = (snapToDraggedDoc: boolean = false) => { + dragEnding = () => { + this.GroupChildDrag = false; + SnappingManager.clearSnapLines(); + }; + @action + dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean, 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.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }); const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => 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 - !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 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) + ); const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.getTransform().inverse(); snappableDocs - .filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)) + .filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc)))) .forEach(doc => { const { left, top, width, height } = docDims(doc); const topLeftInScreen = invXf.transformPoint(left, top); @@ -1896,7 +1891,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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); + SnappingManager.addSnapLines(horizLines, vertLines); }; incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; @@ -1926,16 +1921,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } - showPresPaths = () => (CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.getPaths(this.rootDoc) : null); - brushedView = () => this._brushedView; - gridColor = () => { - const backColor = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor + ':box'); - return lightOrDark(backColor); - }; + gridColor = () => + DashColor(lightOrDark(this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor))) + .fade(0.6) + .toString(); @computed get marqueeView() { TraceMobx(); - return ( + return this._firstRender ? ( + this.placeholder + ) : ( <MarqueeView {...this.props} ref={this._marqueeViewRef} @@ -1960,7 +1955,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}> {this.layoutDoc._freeform_backgroundGrid ? ( <div> - <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!? + <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!? PanelWidth={this.props.PanelWidth} PanelHeight={this.props.PanelHeight} panX={this.panX} @@ -1976,13 +1971,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> ) : null} <CollectionFreeFormViewPannableContents + rootDoc={this.rootDoc} brushedView={this.brushedView} isAnnotationOverlay={this.isAnnotationOverlay} - isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable} transform={this.contentTransform} - zoomScaling={this.zoomScaling} - presPaths={this.showPresPaths} - presPinView={BoolCast(this.Document.config_pinView)} 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} @@ -2007,12 +1999,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number) => { this._brushtimer1 && clearTimeout(this._brushtimer1); this._brushtimer && clearTimeout(this._brushtimer); - this._brushedView = { width: 0, height: 0, panX: 0, panY: 0, opacity: 0 }; + this._brushedView = undefined; this._brushtimer1 = setTimeout( action(() => { - this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2, opacity: 1 }; + this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 }; this._brushtimer = setTimeout( - action(() => (this._brushedView.opacity = 0)), + action(() => (this._brushedView = undefined)), 2500 ); }), @@ -2044,7 +2036,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onClick={this.onClick} onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} - onDrop={this.onExternalDrop.bind(this)} + onDrop={this.onExternalDrop} onDragOver={e => e.preventDefault()} onContextMenu={this.onContextMenu} style={{ @@ -2081,19 +2073,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> ) : ( <> - {this._firstRender ? this.placeholder : this.marqueeView} + {this.marqueeView} {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} - - {/* // uncomment to show snap lines */} - <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}> - <svg style={{ width: '100%', height: '100%' }}> - {(this._hLines ?? []) - .map(l => <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />) // - .concat((this._vLines ?? []).map(l => <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />)) ?? []} - </svg> - </div> - - {this.GroupChildDrag ? <div className="collectionFreeForm-groupDropper" /> : null} + {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />} </> )} </div> @@ -2116,119 +2098,52 @@ class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOv } interface CollectionFreeFormViewPannableContentsProps { - transform: () => string; - zoomScaling: () => number; + rootDoc: Doc; viewDefDivClick?: ScriptField; children?: React.ReactNode | undefined; - //children: () => JSX.Element[]; transition?: string; - presPaths: () => JSX.Element | null; - presPinView?: boolean; isAnnotationOverlay: boolean | undefined; - isAnnotationOverlayScrollable: boolean | undefined; - brushedView: () => { panX: number; panY: number; width: number; height: number; opacity: number }; + transform: () => string; + brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined; } @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> { - @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 ?? ''; - 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(); - if (doc) { - switch (this._drag) { - 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': - 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': - 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': - 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': - doc.style.top = toNumber(doc.offsetTop, e.movementY) + 'px'; - doc.style.left = toNumber(doc.offsetLeft, e.movementX) + 'px'; - } - return false; - } - return true; - }; - @computed get presPaths() { - return !this.props.presPaths() ? null : ( - <> - <div key="presorder">{PresBox.Instance?.order}</div> - <svg key="svg" className="presPaths"> - <defs> - <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible"> - <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" /> - </marker> - <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible"> - <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" /> - </marker> - <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible"> - <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" /> - </marker> - </defs> - {this.props.presPaths()} - </svg> - </> - ); + return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null; } + // rectangle highlight used when following trail/link to a region of a collection that isn't a document + showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => + !viewport ? null : ( + <div + className="collectionFreeFormView-brushView" + style={{ + transform: `translate(${viewport.panX}px, ${viewport.panY}px)`, + width: viewport.width, + height: viewport.height, + border: `orange solid ${viewport.width * 0.005}px`, + }} + /> + ); render() { - const brushedView = this.props.brushedView(); return ( <div className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')} onScroll={e => { 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; + target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars } }} 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} - { - <div - className="collectionFreeFormView-brushView" - style={{ - opacity: brushedView.opacity, - transform: `translate(${brushedView.panX}px, ${brushedView.panY}px)`, - width: brushedView.width, - height: brushedView.height, - border: `orange solid ${brushedView.width * 0.005}px`, - }} - /> - } {this.presPaths} + {this.showViewport(this.props.brushedView())} </div> ); } @@ -2250,9 +2165,9 @@ interface CollectionFreeFormViewBackgroundGridProps { @observer class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> { 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); + if (!this.props.zoomScaling()) return gridSpace; + const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace; + return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2); }; render() { const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50)); @@ -2278,14 +2193,22 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor ctx.clearRect(0, 0, w, h); if (ctx) { ctx.strokeStyle = strokeStyle; + ctx.fillStyle = 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); + if (this.props.zoomScaling() > 1) { + 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); + } + } else { + for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) + for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) { + ctx.fillRect(Math.round(x), Math.round(y), 1, 1); + } } ctx.stroke(); } @@ -2299,20 +2222,21 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) { const browseTransitionTime = 500; SelectionManager.DeselectAll(); - DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { - 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; - for (var cont of containers) { - parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + dv && + DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { + 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; + for (var cont of containers) { + parFfview = parFfview ?? cont.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + } + while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.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.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); + Doc.linkFollowHighlight(dv?.props.Document, false); } - while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.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.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime); - Doc.linkFollowHighlight(dv?.props.Document, false); - } - }); + }); } ScriptingGlobals.add(CollectionBrowseClick); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5c053fefc..a30ec5302 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -377,7 +377,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; newCollection._dragWhenActive = makeGroup; - newCollection.forceActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; newCollection.layout_fitWidth = true; |
