aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-05-10 11:14:57 -0400
committerbobzel <zzzman@gmail.com>2023-05-10 11:14:57 -0400
commit53273651cc8ed0f9e073fa30590e0bb172e492e8 (patch)
treef6b1fd36f99bfa951b99da8ab0e8c70560b031b5 /src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
parentabcf1167340f9f411e7712d11f2110625b0938d8 (diff)
parent97a743455e7fa3eee768b1d4d025b9dedc49f370 (diff)
Merge branch 'master' into collaboration-sarah
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx516
1 files changed, 272 insertions, 244 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0ea614472..29bdc0e2d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { DateField } from '../../../../fields/DateField';
@@ -15,7 +15,7 @@ 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, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
@@ -53,14 +53,17 @@ import { MarqueeView } from './MarqueeView';
import React = require('react');
export type collectionFreeformViewProps = {
+ noPointerWheel?: () => boolean; // turn off pointerwheel interactions (see PDFViewer)
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ originTopLeft?: boolean;
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: string;
- scaleField?: string;
+ viewField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
getScrollHeight?: () => number | undefined;
- dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// 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.
};
@@ -97,8 +100,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private get isAnnotationOverlay() {
return this.props.isAnnotationOverlay;
}
- private get scaleFieldKey() {
- return this.props.scaleField || '_viewScale';
+ public get scaleFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-viewScale' : '_viewScale';
+ }
+ private get panXFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-panX' : '_panX';
+ }
+ private get panYFieldKey() {
+ return this.props.viewField ? this.props.viewField + '-panY' : '_panY';
}
private get borderWidth() {
return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -120,7 +129,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
constructor(props: any) {
super(props);
- this.props.setContentView?.(this);
}
@computed get views() {
@@ -132,7 +140,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+ scale:
+ !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
+ ? 1
+ : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
@computed get fitContentsToBox() {
@@ -150,21 +161,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
);
}
@computed get nativeWidth() {
- return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get nativeHeight() {
- return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get cachedCenteringShiftX(): number {
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
const dv = this.props.DocumentView?.();
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
- return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedGetLocalTransform(): Transform {
return Transform.Identity()
@@ -237,13 +248,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
// panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
- panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
- panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+ panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
+ panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
contentTransform = () =>
- !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
- ? ''
- : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
getTransform = () => this.cachedGetTransform.copy();
getLocalTransform = () => this.cachedGetLocalTransform.copy();
getContainerTransform = () => this.cachedGetContainerTransform.copy();
@@ -255,7 +264,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
@@ -294,17 +303,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
groupFocus = (anchor: Doc, options: DocFocusOptions) => {
- options.docTransform = new Transform(-NumCast(this.rootDoc.panX) + NumCast(anchor.x), -NumCast(this.rootDoc.panY) + NumCast(anchor.y), 1);
+ options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
const res = this.props.focus(this.rootDoc, options);
options.docTransform = undefined;
return res;
};
focus = (anchor: Doc, options: DocFocusOptions) => {
+ if (this._lightboxDoc) return;
const xfToCollection = options?.docTransform ?? Transform.Identity();
- const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+ const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
- const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale || 0.75 : undefined);
+ const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
// focus on the document in the collection
const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
@@ -312,7 +322,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
if (didMove) {
const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
- options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
+ (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
return focusTime;
}
@@ -320,6 +330,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
return new Promise<Opt<DocumentView>>(res => {
+ if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
@@ -341,6 +352,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
@@ -362,6 +374,28 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
(d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
+ if (this.layoutDoc._autoArrange || de.metaKey) {
+ const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
+ sorted.splice(
+ sorted.findIndex(pair => pair.layout === refDoc),
+ 1
+ );
+ if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
+ const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
+ const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
+ const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
+
+ let lastx = NumCast(refDoc.x);
+ let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
+ runInAction(() =>
+ sorted.slice(1).forEach((pair, i) => {
+ lastx = pair.layout.x = lastx + deltax;
+ lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+ })
+ );
+ }
+ }
+
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
return true;
}
@@ -389,7 +423,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { linkRelationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
}
e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
return true;
@@ -431,7 +465,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
if (ptsParent) {
const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+ const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
de.moveDocument = this.props.moveDocument;
@@ -534,7 +568,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
switch (property) {
case StyleProp.BackgroundColor:
const cluster = NumCast(doc?.cluster);
- if (this.Document._useClusters) {
+ if (this.Document._useClusters && doc?.type !== DocumentType.IMG) {
if (this._clusterSets.length <= cluster) {
setTimeout(() => doc && this.updateCluster(doc));
} else {
@@ -656,8 +690,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
ActiveIsInkMask(),
{
title: 'ink stroke',
- x: B.x - ActiveInkWidth() / 2,
- y: B.y - ActiveInkWidth() / 2,
+ 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,
_height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
}
@@ -747,7 +781,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._batch?.end();
};
+ @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 });
@@ -755,7 +791,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
e.preventDefault();
} else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
- if (e.shiftKey) {
+ 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);
@@ -772,7 +808,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PresBox.Instance?.pauseAutoPres();
const dx = e.deltaX;
const dy = e.deltaY;
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
};
@action
@@ -780,11 +816,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PresBox.Instance?.pauseAutoPres();
this.props.DocumentView?.().clearViewTransition();
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
};
+ _eraserLock = 0;
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
@@ -794,6 +831,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
const currPoint = { X: e.clientX, Y: e.clientY };
+ if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed.
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
@@ -801,12 +839,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (!e.shiftKey) {
+ this._eraserLock++;
this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(
+ this.forceStrokeGesture(
+ e,
GestureUtils.Gestures.Stroke,
segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
)
);
+ setTimeout(() => this._eraserLock--);
}
// Lower ink opacity to give the user a visual indicator of deletion.
intersect.inkView.layoutDoc.opacity = 0.5;
@@ -815,14 +856,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
return false;
};
+ forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+ this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
+ };
@action
onPointerMove = (e: PointerEvent): boolean => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
Doc.ActiveTool = InkTool.None;
- if (this.props.isContentActive(true)) e.stopPropagation();
- } else if (!e.cancelBubble) {
+ } else {
if (this.tryDragCluster(e, this._hitCluster)) {
return true;
}
@@ -840,7 +883,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
.filter(
@@ -894,7 +937,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
- segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
+ t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
segment.length && segments.push(segment);
}
// start a new segment from the intersection t value
@@ -934,7 +977,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs
.filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
.forEach(doc => {
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
+ const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -944,106 +987,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
+ const c0 = otherCurve.get(0);
+ const c1 = otherCurve.get(1);
+ const apt = curve.project(c0);
+ const bpt = curve.project(c1);
+ if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
+ tVals.push(apt.t);
+ }
this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
});
+ if (bpt.d !== undefined && bpt.d < 1 && bpt.t !== undefined && !tVals.includes(bpt.t)) {
+ tVals.push(bpt.t);
+ }
}
});
return tVals;
};
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- if (myTouches[0]) {
- if (Doc.ActiveTool === InkTool.None) {
- if (this.tryDragCluster(e, this._hitCluster)) {
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
- document.removeEventListener('pointermove', this.onPointerMove);
- return;
- }
- // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
- this.pan(myTouches[0]);
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- // pinch zooming
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
-
- if (this.prevPoints.size === 2) {
- const oldPoint1 = this.prevPoints.get(pt1.identifier);
- const oldPoint2 = this.prevPoints.get(pt2.identifier);
- if (oldPoint1 && oldPoint2) {
- const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
- // if zooming, zoom
- if (dir !== 0) {
- const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
- const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-
- // calculate the raw delta value
- const rawDelta = dir * (d1 + d2);
-
- // this floors and ceils the delta value to prevent jitteriness
- const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
- this.zoom(centerX, centerY, delta * window.devicePixelRatio);
- this.prevPoints.set(pt1.identifier, pt1);
- this.prevPoints.set(pt2.identifier, pt2);
- }
- // this is not zooming. derive some form of panning from it.
- else {
- // use the centerx and centery as the "new mouse position"
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
-
- this._lastX = centerX;
- this._lastY = centerY;
- }
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- @action
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (this.props.isContentActive(true)) {
- // const pt1: React.Touch | null = e.targetTouches.item(0);
- // const pt2: React.Touch | null = e.targetTouches.item(1);
- // // if (!pt1 || !pt2) return;
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
- if (pt1 && pt2) {
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this._lastX = centerX;
- this._lastY = centerY;
-
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- e.stopPropagation();
- }
- }
- };
-
cleanUpInteractions = () => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -1070,13 +1033,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
- this.setPan(-localTransform.TranslateX / safeScale, NumCast(this.props.Document.scrollTop) * safeScale || -localTransform.TranslateY / safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.Document._isGroup) return; // group style collections neither pan nor zoom
+ if (this.props.noPointerWheel?.() || this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
PresBox.Instance?.pauseAutoPres();
if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
e.stopPropagation();
@@ -1086,7 +1049,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// if ctrl is selected then zoom
if (e.ctrlKey) {
if (this.props.isContentActive(true)) {
- !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
}
} // otherwise pan
else if (this.props.isContentActive(true)) {
@@ -1130,16 +1093,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
}),
{
- xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
}
);
- const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
- else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
+ const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
+ else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+ if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
+ else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
}
}
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
@@ -1169,17 +1135,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}, 10);
newPanY = minPanY;
}
- !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
- !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+ !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
+ !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
}
}
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
+ const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
+ if (collectionDoc?._viewType !== CollectionViewType.Freeform || collectionDoc._panX !== undefined) {
this.setPan(
- NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
- NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
nudgeTime,
true
);
@@ -1191,18 +1158,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
if (sendToBack) {
- doc.zIndex = 0;
+ 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.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[docs.length - 1].zIndex)) : 1;
- if (zlast - docs.length > 100) {
- for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
- zlast = docs.length + 1;
+ 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;
}
};
@@ -1225,8 +1197,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
- this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
- this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+ this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
+ this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
}
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
@@ -1235,9 +1207,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
- if (scale) {
+ if (scale !== undefined) {
const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
- const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ const newScale =
+ scale === 0
+ ? NumCast(this.layoutDoc[this.scaleFieldKey])
+ : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
@@ -1249,8 +1224,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1);
const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1);
- const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
- const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+ const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
+ const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1262,8 +1237,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
}
return {
- panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
- panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+ panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
};
};
@@ -1292,10 +1267,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
pointerEvents = () => {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- const pointerEvents =
- this.props.isContentActive() === false || DocumentDecorations.Instance.Interacting
- ? 'none'
- : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+ const pointerEvents = DocumentDecorations.Instance.Interacting
+ ? 'none'
+ : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
return pointerEvents;
};
getChildDocView(entry: PoolData) {
@@ -1310,12 +1284,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
replica={entry.replica}
suppressSetHeight={this.layoutEngine ? true : false}
renderCutoffProvider={this.renderCutoffProvider}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.Document}
CollectionFreeFormView={this}
LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
+ waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
onClick={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -1327,7 +1300,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={emptyFunction}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
@@ -1343,7 +1316,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
showTitle={this.props.childShowTitle}
- dontScaleFilter={this.props.dontScaleFilter}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
//rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
@@ -1359,7 +1331,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case OpenWhere.inParentFromScreen:
const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.context);
return (
- (this.props.addDocument?.(
+ (this.addDocument?.(
(doc instanceof Doc ? [doc] : doc).map(doc => {
const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
@@ -1373,13 +1345,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case undefined:
case OpenWhere.lightbox:
if (this.layoutDoc._isLightbox) {
- // _isLightbox docs have a script that will unset this overlay onClick
- this.layoutDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
+ this._lightboxDoc = doc;
+ return true;
+ }
+ if (this.childDocList?.includes(doc)) {
+ if (doc.hidden) doc.hidden = false;
return true;
}
}
return this.props.addDocTab(doc, where);
});
+ @observable _lightboxDoc: Opt<Doc>;
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
const childDoc = params.pair.layout;
@@ -1399,7 +1375,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
rotation: Cast(_rotation, 'number'),
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
- opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+ opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
height: _height,
@@ -1551,7 +1527,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
// don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
- if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+ if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
}
@@ -1576,6 +1552,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
componentDidMount() {
+ this.props.setContentView?.(this);
super.componentDidMount?.();
this.props.setBrushViewer?.(this.brushView);
setTimeout(
@@ -1592,7 +1569,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
cbounds => {
if (cbounds) {
const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
const pbounds = {
x: cbounds.x - p[0] + c[0],
y: cbounds.y - p[1] + c[1],
@@ -1602,8 +1579,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) {
this.layoutDoc._width = pbounds.r - pbounds.x;
this.layoutDoc._height = pbounds.b - pbounds.y;
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc[this.panXFieldKey] = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc[this.panYFieldKey] = (cbounds.b + cbounds.y) / 2;
this.layoutDoc.x = pbounds.x;
this.layoutDoc.y = pbounds.y;
}
@@ -1636,7 +1613,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
} else {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image source
+ try {
+ img.src = canvas.toDataURL(); //image source
+ } catch (e) {
+ console.log(e);
+ }
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -1721,8 +1702,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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._panY = NumCast(this.Document._panY) + deltaY / 2;
- this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
+ this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
}
}
e.stopPropagation();
@@ -1746,8 +1727,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
docs.forEach((doc, i) => {
- doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
- doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
+ doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
+ doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
});
};
@@ -1762,20 +1743,31 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
appearanceItems.push({
description: 'Reset View',
event: () => {
- this.props.Document._panX = this.props.Document._panY = 0;
+ this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
this.props.Document[this.scaleFieldKey] = 1;
},
icon: 'compress-arrows-alt',
});
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Toggle auto arrange',
+ event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
+ icon: 'compress-arrows-alt',
+ });
+ if (this.props.setContentView === emptyFunction) {
+ !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+ return;
+ }
!Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
+ appearanceItems.push({ description: (Doc.UserDoc().defaultToFlashcards ? 'Disable' : 'Enable') + ' Flashcard Notes', event: () => (Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards), icon: 'eye' });
appearanceItems.push({
description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
});
appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
- //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
- appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+ !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' });
this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
@@ -1804,27 +1796,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const mores = ContextMenu.Instance.findByDescription('More...');
const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
- moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
!mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
};
- importDocument = (x: number, y: number) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.zip';
- input.onchange = _e => {
- input.files &&
- Doc.importDocument(input.files[0]).then(doc => {
- if (doc instanceof Doc) {
- const [xx, yy] = this.getTransform().transformPoint(x, y);
- (doc.x = xx), (doc.y = yy);
- this.props.addDocument?.(doc);
- }
- });
- };
- input.click();
- };
-
@undoBatch
@action
transcribeStrokes = (math: boolean) => {
@@ -1835,7 +1809,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const lines = text.split('\n');
const height = 30 + 15 * lines.length;
- this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+ this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
}
}
};
@@ -1882,11 +1856,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
});
- children = () => {
+ get children() {
this.incrementalRender();
- const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+ 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" />];
- };
+ }
@computed get placeholder() {
return (
@@ -1930,6 +1904,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
PanelHeight={this.props.PanelHeight}
panX={this.panX}
panY={this.panY}
+ nativeDimScaling={this.nativeDim}
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
isAnnotationOverlay={this.isAnnotationOverlay}
@@ -1964,6 +1939,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
}
+ nativeDim = () => this.nativeDimScaling;
private groupDropDisposer?: DragManager.DragDropDisposer;
protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -1990,13 +1966,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
1000
);
};
-
+ lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
render() {
TraceMobx();
return (
<div
- className={'collectionfreeformview-container'}
- ref={this.createDashEventsTarget}
+ className="collectionfreeformview-container"
+ ref={r => {
+ this.createDashEventsTarget(r);
+ // prevent wheel events from passivly propagating up through containers
+ !this.props.isAnnotationOverlay && r?.addEventListener('wheel', (e: WheelEvent) => this.props.isSelected() && e.preventDefault(), { passive: false });
+ }}
onWheel={this.onPointerWheel}
onClick={this.onClick}
onPointerDown={this.onPointerDown}
@@ -2011,40 +1993,70 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
? 'all'
: (this.props.pointerEvents?.() as any),
+ textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {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" />
- ))}
- {this._vLines?.map(l => (
- <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
- ))}
- </svg>
- </div>
+ {this._lightboxDoc ? (
+ <div style={{ padding: 15, width: '100%', height: '100%' }}>
+ <DocumentView
+ {...this.props}
+ Document={this._lightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.lightboxPanelWidth}
+ PanelHeight={this.lightboxPanelHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ fitContentsToBox={undefined}
+ focus={this.focus}
+ />
+ </div>
+ ) : (
+ <>
+ {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" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
- <div
- className="collectionFreeForm-groupDropper"
- ref={this.createGroupEventsTarget}
- style={{
- width: this.ChildDrag ? '10000' : '100%',
- height: this.ChildDrag ? '10000' : '100%',
- left: this.ChildDrag ? '-5000' : 0,
- top: this.ChildDrag ? '-5000' : 0,
- position: 'absolute',
- background: '#0009930',
- pointerEvents: 'all',
- }}
- />
- ) : null}
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
+ </>
+ )}
</div>
);
}
@@ -2068,7 +2080,8 @@ interface CollectionFreeFormViewPannableContentsProps {
transform: () => string;
zoomScaling: () => number;
viewDefDivClick?: ScriptField;
- children: () => JSX.Element[];
+ children?: React.ReactNode | undefined;
+ //children: () => JSX.Element[];
transition?: string;
presPaths: () => JSX.Element | null;
presPinView?: boolean;
@@ -2162,7 +2175,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
//willChange: "transform"
}}>
- {this.props.children()}
+ {this.props.children}
{!this.props.brushView.width ? null : (
<div
className="collectionFreeFormView-brushView"
@@ -2190,6 +2203,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
isAnnotationOverlay?: boolean;
+ nativeDimScaling: () => number;
zoomScaling: () => number;
layoutDoc: Doc;
cachedCenteringShiftX: number;
@@ -2207,10 +2221,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
const renderGridSpace = gridSpace * this.props.zoomScaling();
- const w = this.props.PanelWidth() + 2 * renderGridSpace;
- const h = this.props.PanelHeight() + 2 * renderGridSpace;
+ const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+ const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
- return (
+ return !this.props.nativeDimScaling() ? null : (
<canvas
className="collectionFreeFormView-grid"
width={w}
@@ -2257,9 +2271,11 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
+ const ffview = selfFfview && selfFfview.rootDoc[selfFfview.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), 0.5, browseTransitionTime);
Doc.linkFollowHighlight(dv?.props.Document, false);
+ } else {
+ DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
}
}
ScriptingGlobals.add(CollectionBrowseClick);
@@ -2274,11 +2290,17 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
});
-ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
- !readOnly &&
- SelectionManager.Views().forEach(view =>
- TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })
- );
+ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
+ SelectionManager.Views().forEach(view =>
+ view.props.pinToPres(view.rootDoc, {
+ currentFrame: Cast(view.rootDoc.currentFrame, 'number', null),
+ pinData: {
+ poslayoutview: pinContent,
+ dataview: pinContent,
+ },
+ pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()),
+ })
+ );
});
ScriptingGlobals.add(function bringToFront() {
SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
@@ -2286,3 +2308,9 @@ ScriptingGlobals.add(function bringToFront() {
ScriptingGlobals.add(function sendToBack(doc: Doc) {
SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
});
+ScriptingGlobals.add(function resetView() {
+ SelectionManager.Docs().forEach(doc => {
+ doc._panX = doc._panY = 0;
+ doc._viewScale = 1;
+ });
+});