aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx553
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx29
4 files changed, 269 insertions, 318 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..da0f7c893 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;
@@ -64,8 +64,6 @@ export type collectionFreeformViewProps = {
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
getScrollHeight?: () => number | undefined;
- 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
@@ -82,6 +80,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 +91,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,21 +116,14 @@ 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[][] = [];
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
- @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 +147,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)));
@@ -251,7 +241,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.layoutDoc._freeform_scale = vals.scale;
};
freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
- reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
+ ignoreNativeDimScaling = () => (this.fitContentsToBox ? true : false);
// freeform_panx, freeform_pany, freeform_scale 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
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
@@ -317,6 +307,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 +458,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 +635,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 +710,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 +728,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 +803,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 +1169,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 +1278,22 @@ 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
+ 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;
+ : this.props.childPointerEvents?.() ??
+ (this.props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+ this.isContentActive() === false
+ ? 'none'
+ : this.props.pointerEvents?.());
+ console.log(`${this.rootDoc.title} pe = ` + pointerevents);
+ return pointerevents;
}
- pointerEvents = () => this._pointerEvents;
+
+ @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined;
+ childPointerEventsFunc = () => this._childPointerEvents;
childContentsActive = () => (this.props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)();
getChildDocView(entry: PoolData) {
const childLayout = entry.pair.layout;
@@ -1308,8 +1303,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 +1324,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}
@@ -1343,8 +1340,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
sizeProvider={this.childSizeProvider}
bringToFront={this.bringToFront}
layout_showTitle={this.props.childlayout_showTitle}
- dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
- pointerEvents={this.pointerEvents}
+ dontRegisterView={this.props.dontRegisterView}
+ pointerEvents={this.childPointerEventsFunc}
//fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.treeView_FreezeChildDimensions)} // bcz: check this
/>
);
@@ -1383,9 +1380,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
@observable _lightboxDoc: Opt<Doc>;
- getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
+ getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData {
const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min);
- const childDoc = params.pair.layout;
+ const childDoc = pair.layout;
const childDocLayout = Doc.Layout(childDoc);
const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
@@ -1411,7 +1408,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
width: _width,
height: _height,
transition: StrCast(childDocLayout.dataTransition),
- pair: params.pair,
+ pair,
+ pointerEvents: Cast(childDoc.pointerEvents, 'string', null),
replica: '',
};
}
@@ -1490,7 +1488,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
doFreeformLayout(poolData: Map<string, PoolData>) {
- 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)));
return [] as ViewDefResult[];
}
@@ -1512,37 +1510,39 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _numLoaded = 1;
_lastPoolSize = 0;
- get doLayoutComputation() {
- const { newPool, computedElementData } = this.doInternalLayoutComputation;
+ @action
+ doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
const array = Array.from(newPool.entries());
- let somethingChanged = array.length !== this._lastPoolSize;
this._lastPoolSize = array.length;
- runInAction(() => {
- 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
- ) {
- this._layoutPoolData.set(entry[0], newPos);
- somethingChanged = true;
- }
- if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
- this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
- somethingChanged = true;
- }
+ 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 (!somethingChanged) return undefined;
+ if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
+ this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
+ }
+ }
+ // by returning undefined, we prevent an edit being made to layoutElements when nothing has happened
+ // this short circuit, prevents lots of downstream mobx invalidations which would have no effect but cause
+ // a distinct lag at the start of dragging.
+ // The reason we're here in the first place without a change is that when dragging a document,
+ // filters are changed on the annotation layers (eg. WebBox) which invalidate the childDoc list
+ // for the overlay views -- however, in many cases, this filter change doesn't actually affect anything
+ // (e.g, no annotations, or only opaque annotations).
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
@@ -1560,7 +1560,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
- }
+ };
getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
// create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
@@ -1577,11 +1577,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return anchor;
};
- @action
componentDidMount() {
this.props.setContentView?.(this);
super.componentDidMount?.();
- this.props.setBrushViewer?.(this.brushView);
setTimeout(
action(() => {
this._firstRender = false;
@@ -1616,26 +1614,31 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
{ fireImmediately: true }
);
- this._disposers.layoutComputation = reaction(
- () => this.doLayoutComputation,
- elements => {
- if (elements !== undefined) this._layoutElements = elements || [];
+ this._disposers.pointerevents = reaction(
+ () => {
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ return DocumentView.Interacting
+ ? 'none'
+ : this.props.childPointerEvents?.() ??
+ (this.props.viewDefDivClick || //
+ (engine === computePassLayout.name && !this.props.isSelected(true)) ||
+ this.isContentActive() === false
+ ? 'none'
+ : this.props.pointerEvents?.());
},
- { fireImmediately: true, name: 'doLayout' }
+ pointerevents => (this._childPointerEvents = pointerevents as any),
+ { fireImmediately: true }
);
- this._disposers.active = reaction(
- () => this.isContentActive(),
- active => this.rootDoc[this.autoResetFieldKey] && !active && this.resetView()
+ this._disposers.layoutComputation = reaction(
+ () => this.doInternalLayoutComputation,
+ ({ newPool, computedElementData }) => (this._layoutElements = this.doLayoutComputation(newPool, computedElementData)),
+ { fireImmediately: true, name: 'layoutComputationReaction' }
);
- this._disposers.fitContent = reaction(
- () => this.rootDoc.fitContentOnce,
- fitContentOnce => {
- if (fitContentOnce) this.fitContentOnce();
- this.rootDoc.fitContentOnce = undefined;
- },
- { fireImmediately: true, name: 'fitContent' }
+ 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()
);
})
);
@@ -1724,7 +1727,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
componentWillUnmount() {
Object.values(this._disposers).forEach(disposer => disposer?.());
- this._marqueeRef?.removeEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}
@action
@@ -1732,26 +1734,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
};
- @action
- onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
- if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
- (e as any).handlePan = true;
-
- if (!this.layoutDoc._freeform_noAutoPan && !this.props.renderDepth && this._marqueeRef) {
- const dragX = e.detail.clientX;
- const dragY = e.detail.clientY;
- const bounds = this._marqueeRef?.getBoundingClientRect();
-
- const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
- const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
- if (deltaX !== 0 || deltaY !== 0) {
- this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
- this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
- }
- }
- e.stopPropagation();
- };
-
@undoBatch
promoteCollection = () => {
const childDocs = this.childDocs.slice();
@@ -1826,16 +1808,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 +1848,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 +1888,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,13 +1918,43 @@ 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 backgroundGrid() {
+ return (
+ <div>
+ <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}
+ panY={this.panY}
+ color={this.gridColor}
+ nativeDimScaling={this.nativeDim}
+ zoomScaling={this.zoomScaling}
+ layoutDoc={this.layoutDoc}
+ isAnnotationOverlay={this.isAnnotationOverlay}
+ cachedCenteringShiftX={this.cachedCenteringShiftX}
+ cachedCenteringShiftY={this.cachedCenteringShiftY}
+ />
+ </div>
+ );
+ }
+ @computed get pannableContents() {
+ return (
+ <CollectionFreeFormViewPannableContents
+ rootDoc={this.rootDoc}
+ brushedView={this.brushedView}
+ isAnnotationOverlay={this.isAnnotationOverlay}
+ transform={this.contentTransform}
+ 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}
+ </CollectionFreeFormViewPannableContents>
+ );
+ }
@computed get marqueeView() {
TraceMobx();
return (
@@ -1950,44 +1972,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
+ panXFieldKey={this.panXFieldKey}
+ panYFieldKey={this.panYFieldKey}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <div
- className="marqueeView-div"
- ref={r => {
- this._marqueeRef = r;
- r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
- }}
- 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!!?
- PanelWidth={this.props.PanelWidth}
- PanelHeight={this.props.PanelHeight}
- panX={this.panX}
- panY={this.panY}
- color={this.gridColor}
- nativeDimScaling={this.nativeDim}
- zoomScaling={this.zoomScaling}
- layoutDoc={this.layoutDoc}
- isAnnotationOverlay={this.isAnnotationOverlay}
- cachedCenteringShiftX={this.cachedCenteringShiftX}
- cachedCenteringShiftY={this.cachedCenteringShiftY}
- />
- </div>
- ) : null}
- <CollectionFreeFormViewPannableContents
- 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}
- </CollectionFreeFormViewPannableContents>
- </div>
+ {this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null}
+ {this.pannableContents}
{this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : null}
</MarqueeView>
);
@@ -2007,12 +1996,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 +2033,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={{
@@ -2083,17 +2072,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
<>
{this._firstRender ? this.placeholder : 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 +2095,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 +2162,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 +2190,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 +2219,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..edcc17afd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -7,7 +7,7 @@ import { InkData, InkField, InkTool } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { RichTextField } from '../../../../fields/RichTextField';
import { Cast, FieldValue, NumCast, StrCast } from '../../../../fields/Types';
-import { ImageField } from '../../../../fields/URLField';
+import { ImageField, nullAudio } from '../../../../fields/URLField';
import { GetEffectiveAcl } from '../../../../fields/util';
import { intersectRect, lightOrDark, returnFalse, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
@@ -35,6 +35,8 @@ interface MarqueeViewProps {
selectDocuments: (docs: Doc[]) => void;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
+ panXFieldKey: string;
+ panYFieldKey: string;
trySelectCluster: (addToSel: boolean) => boolean;
nudge?: (x: number, y: number, nudgeTime?: number) => boolean;
ungroup?: () => void;
@@ -377,7 +379,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;
@@ -652,11 +653,35 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
</div>
);
}
+ MarqueeRef: HTMLDivElement | null = null;
+ @action
+ onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
+ (e as any).handlePan = true;
+
+ const bounds = this.MarqueeRef?.getBoundingClientRect();
+ if (!this.props.Document._freeform_noAutoPan && !this.props.renderDepth && bounds) {
+ const dragX = e.detail.clientX;
+ const dragY = e.detail.clientY;
+
+ const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
+ const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
+ if (deltaX !== 0 || deltaY !== 0) {
+ this.props.Document[this.props.panYFieldKey] = NumCast(this.props.Document[this.props.panYFieldKey]) + deltaY / 2;
+ this.props.Document[this.props.panXFieldKey] = NumCast(this.props.Document[this.props.panXFieldKey]) + deltaX / 2;
+ }
+ }
+ e.stopPropagation();
+ };
render() {
return (
<div
className="marqueeView"
+ ref={r => {
+ r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
+ this.MarqueeRef = r;
+ }}
style={{
overflow: StrCast(this.props.Document._overflow),
cursor: [InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) || this._visible ? 'crosshair' : 'pointer',